@finos/legend-application
Version:
Legend application core
255 lines • 9.62 kB
JavaScript
/**
* Copyright (c) 2020-present, Goldman Sachs
*
* 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.
*/
import { action, makeObservable, observable, computed } from 'mobx';
import { guaranteeNonEmptyString, uuid, isNonNullable, ActionState, FuzzySearchEngine, FuzzySearchAdvancedConfigState, } from '@finos/legend-shared';
export var VIRTUAL_ASSISTANT_TAB;
(function (VIRTUAL_ASSISTANT_TAB) {
VIRTUAL_ASSISTANT_TAB["SEARCH"] = "SEARCH";
VIRTUAL_ASSISTANT_TAB["CONTEXTUAL_SUPPORT"] = "CONTEXTUAL_SUPPORT";
})(VIRTUAL_ASSISTANT_TAB || (VIRTUAL_ASSISTANT_TAB = {}));
export const DOCUMENTATION_SEARCH_RESULTS_LIMIT = 100;
export class VirtualAssistantDocumentationEntry {
uuid = uuid();
documentationKey;
title;
content;
url;
isOpen = false;
constructor(docEntry) {
makeObservable(this, {
isOpen: observable,
setIsOpen: action,
});
this.documentationKey = docEntry.key;
this.title = guaranteeNonEmptyString(docEntry.title);
this.content = docEntry.markdownText ?? docEntry.text;
this.url = docEntry.url;
}
setIsOpen(val) {
this.isOpen = val;
}
}
export class VirtualAssistantContextualDocumentationEntry {
uuid = uuid();
context;
title;
content;
url;
related;
constructor(context, docEntry, related) {
this.context = context;
this.title = docEntry.title;
this.content = docEntry.markdownText ?? docEntry.text;
this.url = docEntry.url;
this.related = related;
}
}
/**
* NOTE: since we're displaying the documentation entry in virtual assistant
* we want only user-friendly docs, we will discard anything that doesn't
* come with a title, or does not have any content/url
*/
export const isValidVirtualAssistantDocumentationEntry = (entry) => Boolean(entry.title && (entry.url ?? entry.text ?? entry.markdownText));
/**
* Check if the documentation entry should be displayed in virtual assistant,
* i.e. it has some text content, rather just a link
*/
export const shouldDisplayVirtualAssistantDocumentationEntry = (entry) => isValidVirtualAssistantDocumentationEntry(entry) &&
Boolean(entry.text ?? entry.markdownText);
export class AssistantService {
applicationStore;
/**
* This key is used to allow programmatic re-rendering of the assistant panel
*/
panelRenderingKey = uuid();
isDisabled = false;
isHidden = true; // hide by default unless specified by the application to show
isOpen = false;
isPanelMaximized = false;
selectedTab = VIRTUAL_ASSISTANT_TAB.SEARCH;
currentDocumentationEntry;
// search text
searchEngine;
searchConfigurationState;
searchState = ActionState.create();
searchText = '';
searchResults = [];
showSearchConfigurationMenu = false;
isOverSearchLimit = false;
constructor(applicationStore) {
makeObservable(this, {
isDisabled: observable,
isHidden: observable,
isOpen: observable,
isPanelMaximized: observable,
panelRenderingKey: observable,
isOverSearchLimit: observable,
selectedTab: observable,
searchText: observable,
searchResults: observable,
currentDocumentationEntry: observable,
showSearchConfigurationMenu: observable,
currentContextualDocumentationEntry: computed,
setIsDisabled: action,
setIsHidden: action,
setIsOpen: action,
setIsPanelMaximized: action,
setSelectedTab: action,
setSearchText: action,
resetSearch: action,
search: action,
openDocumentationEntry: action,
refreshPanelRendering: action,
setShowSearchConfigurationMenu: action,
});
this.applicationStore = applicationStore;
this.searchEngine = new FuzzySearchEngine(this.applicationStore.documentationService
.getAllDocEntries()
.filter(isValidVirtualAssistantDocumentationEntry), {
includeScore: true,
shouldSort: true,
// Ignore location when computing the search score
// See https://fusejs.io/concepts/scoring-theory.html
ignoreLocation: true,
// This specifies the point the search gives up
// `0.0` means exact match where `1.0` would match anything
// We set a relatively low threshold to filter out irrelevant results
threshold: 0.2,
keys: [
{
// NOTE: for now, we would weight title the most
name: 'title',
weight: 4,
},
{
name: 'text',
weight: 1,
},
{
name: 'markdownText.value',
weight: 1,
},
],
// extended search allows for exact word match through single quote
// See https://fusejs.io/examples.html#extended-search
useExtendedSearch: true,
});
this.searchConfigurationState = new FuzzySearchAdvancedConfigState(() => {
this.search();
});
}
get currentContextualDocumentationEntry() {
if (!this.applicationStore.navigationContextService.currentContext) {
return undefined;
}
const currentContext = this.applicationStore.navigationContextService.currentContext.key;
const currentContextualDocumentationEntry = this.applicationStore.documentationService.getContextualDocEntry(currentContext);
return currentContextualDocumentationEntry
? new VirtualAssistantContextualDocumentationEntry(currentContext, currentContextualDocumentationEntry, (currentContextualDocumentationEntry.related ?? [])
.map((entry) => this.applicationStore.documentationService.getDocEntry(entry))
.filter(isNonNullable)
.filter(isValidVirtualAssistantDocumentationEntry)
.map((entry) => new VirtualAssistantDocumentationEntry(entry)))
: undefined;
}
openDocumentationEntry(key) {
const entry = this.applicationStore.documentationService.getDocEntry(key);
if (entry) {
this.setIsOpen(true);
this.setIsHidden(false);
this.currentDocumentationEntry = new VirtualAssistantDocumentationEntry(entry);
this.currentDocumentationEntry.setIsOpen(true);
this.resetSearch();
}
}
openDocumentationEntryLink(key) {
const entry = this.applicationStore.documentationService.getDocEntry(key);
if (entry) {
if (shouldDisplayVirtualAssistantDocumentationEntry(entry)) {
this.openDocumentationEntry(entry.key);
}
else if (entry.url) {
this.applicationStore.navigationService.navigator.visitAddress(entry.url);
}
}
}
setIsDisabled(val) {
this.isDisabled = val;
}
setIsHidden(val) {
this.isHidden = val;
}
setIsPanelMaximized(val) {
this.isPanelMaximized = val;
}
hideAssistant() {
this.setIsHidden(true);
this.setIsOpen(false);
}
toggleAssistant() {
const newVal = !this.isHidden;
if (newVal) {
this.hideAssistant();
}
else {
this.setIsHidden(false);
}
}
setIsOpen(val) {
this.isOpen = val;
}
setSelectedTab(val) {
this.selectedTab = val;
}
refreshPanelRendering() {
this.panelRenderingKey = uuid();
}
setSearchText(val) {
this.searchText = val;
}
resetSearch() {
this.searchText = '';
this.searchResults = [];
this.searchState.complete();
}
search() {
if (!this.searchText) {
this.searchResults = [];
return;
}
this.currentDocumentationEntry = undefined;
this.searchState.inProgress();
this.searchResults = Array.from(this.searchEngine
.search(this.searchConfigurationState.generateSearchText(this.searchText), {
// NOTE: search for limit + 1 item so we can know if there are more search results
limit: DOCUMENTATION_SEARCH_RESULTS_LIMIT + 1,
})
.values()).map((result) => new VirtualAssistantDocumentationEntry(result.item));
// check if the search results exceed the limit
if (this.searchResults.length > DOCUMENTATION_SEARCH_RESULTS_LIMIT) {
this.isOverSearchLimit = true;
this.searchResults = this.searchResults.slice(0, DOCUMENTATION_SEARCH_RESULTS_LIMIT);
}
else {
this.isOverSearchLimit = false;
}
this.searchState.complete();
}
setShowSearchConfigurationMenu(val) {
this.showSearchConfigurationMenu = val;
}
}
//# sourceMappingURL=AssistantService.js.map