@finos/legend-server-showcase-deployment
Version:
Legend Showcase server deployment
193 lines • 8.05 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 { get } from 'https';
import { readFileSync } from 'fs';
import { Showcase, } from '@finos/legend-server-showcase';
import { FuzzySearchEngine, promisify, at, ActionState, } from '@finos/legend-shared';
async function fetchExternalLinkSiteData(url) {
return new Promise((resolve, reject) => {
get(url, (response) => {
const chunks_of_data = [];
response.on('data', (fragments) => {
chunks_of_data.push(fragments);
});
response.on('end', () => {
resolve(Buffer.concat(chunks_of_data).toString());
});
response.on('error', (error) => {
reject(error);
});
});
});
}
const fetchShowcasesData = async (datasource) => {
let content;
if (datasource.url) {
content = JSON.parse(await fetchExternalLinkSiteData(datasource.url));
}
else if (datasource.path) {
content = JSON.parse(readFileSync(datasource.path, { encoding: 'utf-8' }));
}
return content && Array.isArray(content) ? content : [];
};
export class ShowcaseRegistry {
config;
// NOTE: maintain these to improve performance
RAW__metadata = [];
RAW__showcaseIndex = new Map();
showcasesIndex = new Map();
showcaseSearchEngine;
fetchDataState = ActionState.create();
// private constructor to enforce singleton
constructor() {
// NOTE: due to the way we export the constructor of `FuzzySearchEngine`, when we run this with ESM
// we can remove this workaround once Fuse supports ESM
// See https://github.com/krisk/Fuse/pull/727
this.showcaseSearchEngine = new FuzzySearchEngine([], {
includeScore: true,
// NOTE: we must not sort/change the order in the grid since
// we want to ensure the element row is on top
shouldSort: false,
// 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: [
{
name: 'title',
weight: 5,
},
{
name: 'description',
weight: 3,
},
{
name: 'path',
weight: 2,
},
{
name: 'documentation',
weight: 1,
},
],
// extended search allows for exact word match through single quote
// See https://fusejs.io/examples.html#extended-search
useExtendedSearch: true,
});
}
static async initialize(config) {
const registry = new ShowcaseRegistry();
registry.config = config;
await registry.fetchData();
return registry;
}
getShowcases() {
return this.RAW__metadata;
}
getShowcase(path) {
return this.RAW__showcaseIndex.get(path);
}
async fetchData() {
if (this.fetchDataState.isInProgress) {
return;
}
try {
this.fetchDataState.inProgress();
const RAW__metadata = [];
const RAW__showcaseIndex = new Map();
const showcasesIndex = new Map();
await Promise.all(this.config?.datasources.map(async (datasource) => {
const content = await fetchShowcasesData(datasource);
content.forEach((showcaseContent) => {
const showcase = Showcase.serialization.fromJson(showcaseContent);
// NOTE: do not allow override
if (!showcasesIndex.has(showcase.path)) {
showcasesIndex.set(showcase.path, showcase);
RAW__showcaseIndex.set(showcase.path, showcaseContent);
RAW__metadata.push({
title: showcase.title,
path: showcase.path,
description: showcase.description,
development: showcase.development,
});
}
});
}) ?? []);
// update in one go
this.RAW__metadata = RAW__metadata;
this.RAW__showcaseIndex = RAW__showcaseIndex;
this.showcasesIndex = showcasesIndex;
this.showcaseSearchEngine.remove(() => true);
this.showcaseSearchEngine.setCollection(Array.from(this.showcasesIndex.values()));
}
finally {
this.fetchDataState.complete();
}
}
async search(searchText) {
// short-circuit when the text search length is too short, else we would end up putting strain on the server
if (searchText.length <= 2) {
return { showcases: [], textMatches: [] };
}
const matches = [];
// NOTE: for text search, we only support case-insensitive search now
const lowerCaseSearchText = searchText.toLowerCase();
await Promise.all(Array.from(this.showcasesIndex.values()).map((showcase) => promisify(() => {
const result = {
path: showcase.path,
matches: [],
preview: [],
};
const previewLines = new Map();
const code = showcase.code;
const lines = code.split('\n');
lines.forEach((line, lineIdx) => {
const lowerCaseLine = line.toLowerCase();
let fromIdx = 0;
let currentMatchIdx = lowerCaseLine.indexOf(lowerCaseSearchText, fromIdx);
while (currentMatchIdx !== -1) {
const previewTextStartLineIdx = Math.max(lineIdx - 1, 0);
previewLines.set(previewTextStartLineIdx + 1, at(lines, previewTextStartLineIdx));
previewLines.set(lineIdx + 1, at(lines, lineIdx));
const previewTextEndLineIdx = Math.min(lineIdx + 1, lines.length - 1);
previewLines.set(previewTextEndLineIdx + 1, at(lines, previewTextEndLineIdx));
result.matches.push({
line: lineIdx + 1,
startColumn: currentMatchIdx + 1,
endColumn: currentMatchIdx + 1 + lowerCaseSearchText.length,
});
fromIdx = currentMatchIdx + lowerCaseSearchText.length;
currentMatchIdx = lowerCaseLine.indexOf(lowerCaseSearchText, fromIdx);
}
});
if (!result.matches.length) {
return;
}
result.preview = Array.from(previewLines.entries())
.map(([line, text]) => ({ line, text }))
.sort((a, b) => a.line - b.line);
matches.push(result);
})));
return {
showcases: Array.from(this.showcaseSearchEngine.search(searchText).values()).map((result) => result.item.path),
textMatches: matches,
};
}
}
//# sourceMappingURL=ShowcaseRegistry.js.map