Search & Notebook Tagging - Mct4820 (#5203)

* implement new search and tagging for notebooks
* add example tags, remove inspector reference
* include annotations in mct
* fix performance tests


Co-authored-by: John Hill <john.c.hill@nasa.gov>
Co-authored-by: unlikelyzero <jchill2@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
Scott Bell
2022-06-03 22:12:42 +02:00
committed by GitHub
parent 2aec1ee854
commit 3c70cf1767
57 changed files with 3220 additions and 929 deletions

View File

@@ -18,6 +18,9 @@
}"
>
<CreateButton class="l-shell__create-button" />
<GrandSearch
ref="grand-search"
/>
<indicators class="l-shell__head-section l-shell__indicators" />
<button
class="l-shell__head__collapse-button c-icon-button"
@@ -122,6 +125,7 @@ import Inspector from '../inspector/Inspector.vue';
import MctTree from './mct-tree.vue';
import ObjectView from '../components/ObjectView.vue';
import CreateButton from './CreateButton.vue';
import GrandSearch from './search/GrandSearch.vue';
import multipane from './multipane.vue';
import pane from './pane.vue';
import BrowseBar from './BrowseBar.vue';
@@ -136,6 +140,7 @@ export default {
MctTree,
ObjectView,
CreateButton,
GrandSearch,
multipane,
pane,
BrowseBar,

View File

@@ -1,13 +0,0 @@
<template>
<div class="c-search c-search--major">
<input
type="search"
placeholder="Search"
>
</div>
</template>
<script>
export default {
};
</script>

View File

@@ -1,10 +0,0 @@
/******************************* SEARCH */
.c-search {
input[type=search] {
width: 100%;
}
&--major {
display: flex;
}
}

View File

@@ -12,6 +12,7 @@
class="c-tree-and-search__search"
>
<search
v-show="isSelectorTree"
ref="shell-search"
class="c-search"
:value="searchValue"

View File

@@ -0,0 +1,148 @@
/*****************************************************************************
* 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.
*****************************************************************************/
<template>
<div
class="c-gsearch-result c-gsearch-result--annotation"
>
<div
class="c-gsearch-result__type-icon"
:class="resultTypeIcon"
></div>
<div
class="c-gsearch-result__body"
aria-label="Annotation Search Result"
>
<div
class="c-gsearch-result__title"
@click="clickedResult"
>
{{ getResultName }}
</div>
<ObjectPath
ref="location"
:read-only="false"
/>
<div class="c-gsearch-result__tags">
<div
v-for="(tag, index) in result.fullTagModels"
:key="index"
class="c-tag"
:class="{ '--is-not-search-match': !isSearchMatched(tag) }"
:style="{ backgroundColor: tag.backgroundColor, color: tag.foregroundColor }"
>
{{ tag.label }}
</div>
</div>
</div>
<div class="c-gsearch-result__more-options-button">
<button class="c-icon-button icon-3-dots"></button>
</div>
</div>
</template>
<script>
import ObjectPath from '../../components/ObjectPath.vue';
import objectPathToUrl from '../../../tools/url';
export default {
name: 'AnnotationSearchResult',
components: {
ObjectPath
},
inject: ['openmct'],
props: {
result: {
type: Object,
required: true,
default() {
return {};
}
}
},
data() {
return {
};
},
computed: {
domainObject() {
return this.result.targetModels[0];
},
getResultName() {
if (this.result.annotationType === this.openmct.annotation.ANNOTATION_TYPES.NOTEBOOK) {
const targetID = Object.keys(this.result.targets)[0];
const entryIdToFind = this.result.targets[targetID].entryId;
const notebookModel = this.result.targetModels[0].configuration.entries;
const sections = Object.values(notebookModel);
for (const section of sections) {
const pages = Object.values(section);
for (const entries of pages) {
for (const entry of entries) {
if (entry.id === entryIdToFind) {
return entry.text;
}
}
}
}
return "Could not find any matching Notebook entries";
} else {
return this.result.targetModels[0].name;
}
},
resultTypeIcon() {
return this.openmct.types.get(this.result.type).definition.cssClass;
},
tagBackgroundColor() {
return this.result.fullTagModels[0].backgroundColor;
},
tagForegroundColor() {
return this.result.fullTagModels[0].foregroundColor;
}
},
mounted() {
const selectionObject = {
context: {
item: this.domainObject
}
};
this.$refs.location.updateSelection([[selectionObject]]);
},
methods: {
clickedResult() {
const objectPath = this.domainObject.originalPath;
const resultUrl = objectPathToUrl(this.openmct, objectPath);
this.openmct.router.navigate(resultUrl);
},
isSearchMatched(tag) {
if (this.result.matchingTagKeys) {
return this.result.matchingTagKeys.includes(tag.tagID);
}
return false;
}
}
};
</script>

View File

@@ -0,0 +1,145 @@
/*****************************************************************************
* 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.
*****************************************************************************/
<template>
<div
ref="GrandSearch"
aria-label="OpenMCT Search"
class="c-gsearch"
role="searchbox"
>
<search
ref="shell-search"
class="c-gsearch__input"
tabindex="0"
:value="searchValue"
@input="searchEverything"
@clear="searchEverything"
@click="showSearchResults"
/>
<SearchResultsDropDown
ref="searchResultsDropDown"
/>
</div>
</template>
<script>
import search from '../../components/search.vue';
import SearchResultsDropDown from './SearchResultsDropDown.vue';
export default {
name: 'GrandSearch',
components: {
search,
SearchResultsDropDown
},
inject: ['openmct'],
props: {
},
data() {
return {
searchValue: '',
searchLoading: false,
annotationSearchResults: [],
objectSearchResults: []
};
},
mounted() {
},
destroyed() {
document.body.removeEventListener('click', this.handleOutsideClick);
},
methods: {
async searchEverything(value) {
// if an abort controller exists, regardless of the value passed in,
// there is an active search that should be canceled
if (this.abortSearchController) {
this.abortSearchController.abort();
delete this.abortSearchController;
}
this.searchValue = value;
this.searchLoading = true;
// clear any previous search results
this.annotationSearchResults = [];
this.objectSearchResults = [];
if (this.searchValue) {
await this.getSearchResults();
} else {
this.searchLoading = false;
this.$refs.searchResultsDropDown.showResults(this.annotationSearchResults, this.objectSearchResults);
}
},
getPathsForObjects(objectsNeedingPaths) {
return Promise.all(objectsNeedingPaths.map(async (domainObject) => {
const keyStringForObject = this.openmct.objects.makeKeyString(domainObject.identifier);
const originalPathObjects = await this.openmct.objects.getOriginalPath(keyStringForObject);
return {
originalPath: originalPathObjects,
...domainObject
};
}));
},
async getSearchResults() {
// an abort controller will be passed in that will be used
// to cancel an active searches if necessary
this.abortSearchController = new AbortController();
const abortSignal = this.abortSearchController.signal;
try {
this.annotationSearchResults = await this.openmct.annotation.searchForTags(this.searchValue, abortSignal);
const fullObjectSearchResults = await Promise.all(this.openmct.objects.search(this.searchValue, abortSignal));
const aggregatedObjectSearchResults = fullObjectSearchResults.flat();
const aggregatedObjectSearchResultsWithPaths = await this.getPathsForObjects(aggregatedObjectSearchResults);
const filterAnnotations = aggregatedObjectSearchResultsWithPaths.filter(result => {
return result.type !== 'annotation';
});
this.objectSearchResults = filterAnnotations;
this.showSearchResults();
} catch (error) {
console.error(`😞 Error searching`, error);
this.searchLoading = false;
if (this.abortSearchController) {
delete this.abortSearchController;
}
}
},
showSearchResults() {
this.$refs.searchResultsDropDown.showResults(this.annotationSearchResults, this.objectSearchResults);
document.body.addEventListener('click', this.handleOutsideClick);
},
handleOutsideClick(event) {
// if click event is detected outside the dropdown while the
// dropdown is visible, this will collapse the dropdown.
if (this.$refs.GrandSearch) {
const clickedInsideDropdown = this.$refs.GrandSearch.contains(event.target);
if (!clickedInsideDropdown && this.$refs.searchResultsDropDown._data.resultsShown) {
this.$refs.searchResultsDropDown._data.resultsShown = false;
}
}
}
}
};
</script>

View File

@@ -0,0 +1,102 @@
/*****************************************************************************
* 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.
*****************************************************************************/
<template>
<div
class="c-gsearch-result c-gsearch-result--object"
role="presentation"
>
<div
class="c-gsearch-result__type-icon"
:class="resultTypeIcon"
></div>
<div
class="c-gsearch-result__body"
role="option"
:aria-label="`${resultName} ${resultType} result`"
>
<div
class="c-gsearch-result__title"
:name="resultName"
@click="clickedResult"
>
{{ resultName }}
</div>
<ObjectPath
ref="objectpath"
:read-only="false"
/>
</div>
<div class="c-gsearch-result__more-options-button">
<button class="c-icon-button icon-3-dots"></button>
</div>
</div>
</template>
<script>
import ObjectPath from '../../components/ObjectPath.vue';
import objectPathToUrl from '../../../tools/url';
export default {
name: 'ObjectSearchResult',
components: {
ObjectPath
},
inject: ['openmct'],
props: {
result: {
type: Object,
required: true,
default() {
return {};
}
}
},
computed: {
resultName() {
return this.result.name;
},
resultTypeIcon() {
return this.openmct.types.get(this.result.type).definition.cssClass;
},
resultType() {
return this.result.type;
}
},
mounted() {
const selectionObject = {
context: {
item: this.result
}
};
this.$refs.objectpath.updateSelection([[selectionObject]]);
},
methods: {
clickedResult() {
const objectPath = this.result.originalPath;
const resultUrl = objectPathToUrl(this.openmct, objectPath);
this.openmct.router.navigate(resultUrl);
}
}
};
</script>

View File

@@ -0,0 +1,99 @@
/*****************************************************************************
* 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.
*****************************************************************************/
<template>
<div
v-if="(annotationResults && annotationResults.length) ||
(objectResults && objectResults.length)"
class="c-gsearch__dropdown"
>
<div
v-show="resultsShown"
class="c-gsearch__results-wrapper"
>
<div class="c-gsearch__results">
<div
v-if="objectResults && objectResults.length"
ref="objectResults"
class="c-gsearch__results-section"
role="listbox"
>
<div class="c-gsearch__results-section-title">Object Results</div>
<object-search-result
v-for="(objectResult, index) in objectResults"
:key="index"
:result="objectResult"
@click.native="selectedResult"
/>
</div>
<div
v-if="annotationResults && annotationResults.length"
ref="annotationResults"
>
<div class="c-gsearch__results-section-title">Annotation Results</div>
<annotation-search-result
v-for="(annotationResult, index) in annotationResults"
:key="index"
:result="annotationResult"
@click.native="selectedResult"
/>
</div>
</div>
</div>
</div>
</template>
<script>
import AnnotationSearchResult from './AnnotationSearchResult.vue';
import ObjectSearchResult from './ObjectSearchResult.vue';
export default {
name: 'SearchResultsDropDown',
components: {
AnnotationSearchResult,
ObjectSearchResult
},
data() {
return {
resultsShown: false,
annotationResults: [],
objectResults: []
};
},
methods: {
selectedResult() {
this.resultsShown = false;
},
showResults(passedAnnotationResults, passedObjectResults) {
if ((passedAnnotationResults && passedAnnotationResults.length)
|| (passedObjectResults && passedObjectResults.length)) {
this.resultsShown = true;
this.annotationResults = passedAnnotationResults;
this.objectResults = passedObjectResults;
} else {
this.resultsShown = false;
}
}
},
template: 'Dropdown'
};
</script>

View File

@@ -0,0 +1,137 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/******************************* EXPANDED SEARCH 2022 */
.c-gsearch {
.l-shell__head & {
// Search input in the shell head
width: 20%;
.c-search {
background: rgba($colorHeadFg, 0.2);
box-shadow: none;
}
}
&__results-wrapper {
@include menuOuter();
display: flex;
flex-direction: column;
padding: $interiorMarginLg;
min-width: 500px;
max-height: 500px;
}
&__results,
&__results-section {
flex: 1 1 auto;
}
&__results {
// Holds n __results-sections
padding-right: $interiorMargin; // Fend off scrollbar
overflow-y: auto;
> * + * {
margin-top: $interiorMarginLg;
}
}
&__results-section {
> * + * {
margin-top: $interiorMarginSm;
}
}
&__results-section-title {
@include propertiesHeader();
}
}
.c-gsearch-result {
display: flex;
padding: $interiorMargin $interiorMarginSm;
> * + * {
margin-left: $interiorMarginLg;
}
+ .c-gsearch-result {
border-top: 1px solid $colorInteriorBorder;
}
&__type-icon,
&__more-options-button {
flex: 0 0 auto;
}
&__type-icon {
color: $colorItemTreeIcon;
font-size: 2.2em;
// TEMP: uses object-label component, hide label part
.c-object-label__name {
display: none;
}
}
&__more-options-button {
display: none; // TEMP until enabled
}
&__body {
flex: 1 1 auto;
> * + * {
margin-top: $interiorMarginSm;
}
.c-location {
font-size: 0.9em;
opacity: 0.8;
}
}
&__tags {
display: flex;
> * + * {
margin-left: $interiorMargin;
}
}
&__title {
border-radius: $basicCr;
color: pullForward($colorBodyFg, 30%);
cursor: pointer;
font-size: 1.15em;
padding: 3px $interiorMarginSm;
&:hover {
background-color: $colorItemTreeHoverBg;
}
}
.c-tag {
font-size: 0.9em;
}
}