@backstage/plugin-search-backend-node
Advanced tools
Comparing version 0.1.4 to 0.2.0
# @backstage/plugin-search-backend-node | ||
## 0.2.0 | ||
### Minor Changes | ||
- 5aff84759: This release represents a move out of a pre-alpha phase of the Backstage Search | ||
plugin, into an alpha phase. With this release, you gain more control over the | ||
layout of your search page on the frontend, as well as the ability to extend | ||
search on the backend to encompass everything Backstage users may want to find. | ||
If you are updating to version `v0.4.0` of `@backstage/plugin-search` from a | ||
prior release, you will need to make modifications to your app backend. | ||
First, navigate to your backend package and install the two related search | ||
backend packages: | ||
```sh | ||
cd packages/backend | ||
yarn add @backstage/plugin-search-backend @backstage/plugin-search-backend-node | ||
``` | ||
Wire up these new packages into your app backend by first creating a new | ||
`search.ts` file at `src/plugins/search.ts` with contents like the following: | ||
```typescript | ||
import { useHotCleanup } from '@backstage/backend-common'; | ||
import { createRouter } from '@backstage/plugin-search-backend'; | ||
import { | ||
IndexBuilder, | ||
LunrSearchEngine, | ||
} from '@backstage/plugin-search-backend-node'; | ||
import { PluginEnvironment } from '../types'; | ||
import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; | ||
export default async function createPlugin({ | ||
logger, | ||
discovery, | ||
}: PluginEnvironment) { | ||
// Initialize a connection to a search engine. | ||
const searchEngine = new LunrSearchEngine({ logger }); | ||
const indexBuilder = new IndexBuilder({ logger, searchEngine }); | ||
// Collators are responsible for gathering documents known to plugins. This | ||
// particular collator gathers entities from the software catalog. | ||
indexBuilder.addCollator({ | ||
defaultRefreshIntervalSeconds: 600, | ||
collator: new DefaultCatalogCollator({ discovery }), | ||
}); | ||
// The scheduler controls when documents are gathered from collators and sent | ||
// to the search engine for indexing. | ||
const { scheduler } = await indexBuilder.build(); | ||
// A 3 second delay gives the backend server a chance to initialize before | ||
// any collators are executed, which may attempt requests against the API. | ||
setTimeout(() => scheduler.start(), 3000); | ||
useHotCleanup(module, () => scheduler.stop()); | ||
return await createRouter({ | ||
engine: indexBuilder.getSearchEngine(), | ||
logger, | ||
}); | ||
} | ||
``` | ||
Then, ensure the search plugin you configured above is initialized by modifying | ||
your backend's `index.ts` file in the following ways: | ||
```diff | ||
+import search from './plugins/search'; | ||
// ... | ||
+const searchEnv = useHotMemoize(module, () => createEnv('search')); | ||
// ... | ||
+apiRouter.use('/search', await search(searchEnv)); | ||
// ... | ||
``` | ||
### Patch Changes | ||
- db1c8f93b: The `<Search...Next /> set of components exported by the Search Plugin are now updated to use the Search Backend API. These will be made available as the default non-"next" versions in a follow-up release. | ||
The interfaces for decorators and collators in the Search Backend have also seen minor, breaking revisions ahead of a general release. If you happen to be building on top of these interfaces, check and update your implementations accordingly. The APIs will be considered more stable in a follow-up release. | ||
- Updated dependencies [db1c8f93b] | ||
- @backstage/search-common@0.1.2 | ||
## 0.1.4 | ||
@@ -4,0 +89,0 @@ |
@@ -22,8 +22,7 @@ 'use strict'; | ||
addCollator({ | ||
type, | ||
collator, | ||
defaultRefreshIntervalSeconds | ||
}) { | ||
this.logger.info(`Added ${collator.constructor.name} collator for type ${type}`); | ||
this.collators[type] = { | ||
this.logger.info(`Added ${collator.constructor.name} collator for type ${collator.type}`); | ||
this.collators[collator.type] = { | ||
refreshInterval: defaultRefreshIntervalSeconds, | ||
@@ -33,6 +32,4 @@ collate: collator | ||
} | ||
addDecorator({ | ||
types = ["*"], | ||
decorator | ||
}) { | ||
addDecorator({decorator}) { | ||
const types = decorator.types || ["*"]; | ||
this.logger.info(`Added decorator ${decorator.constructor.name} to types ${types.join(", ")}`); | ||
@@ -86,2 +83,3 @@ types.forEach((type) => { | ||
this.schedule.forEach(({task, interval}) => { | ||
task(); | ||
this.intervalTimeouts.push(setInterval(() => { | ||
@@ -110,7 +108,20 @@ task(); | ||
let lunrQueryFilters; | ||
const lunrTerm = term ? `+${term}` : ""; | ||
if (filters) { | ||
lunrQueryFilters = Object.entries(filters).map(([key, value]) => ` +${key}:${value}`).join(""); | ||
lunrQueryFilters = Object.entries(filters).map(([field, value]) => { | ||
if (["string", "number", "boolean"].includes(typeof value)) { | ||
return ` +${field}:${value}`; | ||
} | ||
if (Array.isArray(value)) { | ||
this.logger.warn(`Non-scalar filter value used for field ${field}. Consider using a different Search Engine for better results.`); | ||
return ` ${value.map((v) => { | ||
return `${field}:${v}`; | ||
})}`; | ||
} | ||
this.logger.warn(`Unknown filter type used on field ${field}`); | ||
return ""; | ||
}).join(""); | ||
} | ||
return { | ||
lunrQueryString: `${term}${lunrQueryFilters || ""}`, | ||
lunrQueryString: `${lunrTerm}${lunrQueryFilters || ""}`, | ||
documentTypes: types || ["*"] | ||
@@ -122,2 +133,5 @@ }; | ||
} | ||
setTranslator(translator) { | ||
this.translator = translator; | ||
} | ||
index(type, documents) { | ||
@@ -139,5 +153,10 @@ const lunrBuilder = new lunr__default['default'].Builder(); | ||
if (documentTypes.length === 1 && documentTypes[0] === "*") { | ||
Object.values(this.lunrIndices).forEach((i) => { | ||
Object.keys(this.lunrIndices).forEach((type) => { | ||
try { | ||
results.push(...i.search(lunrQueryString)); | ||
results.push(...this.lunrIndices[type].search(lunrQueryString).map((result) => { | ||
return { | ||
result, | ||
type | ||
}; | ||
})); | ||
} catch (err) { | ||
@@ -149,5 +168,10 @@ if (err instanceof lunr__default['default'].QueryParseError && err.message.startsWith("unrecognised field")) | ||
} else { | ||
Object.keys(this.lunrIndices).filter((d) => documentTypes.includes(d)).forEach((d) => { | ||
Object.keys(this.lunrIndices).filter((type) => documentTypes.includes(type)).forEach((type) => { | ||
try { | ||
results.push(...this.lunrIndices[d].search(lunrQueryString)); | ||
results.push(...this.lunrIndices[type].search(lunrQueryString).map((result) => { | ||
return { | ||
result, | ||
type | ||
}; | ||
})); | ||
} catch (err) { | ||
@@ -160,10 +184,10 @@ if (err instanceof lunr__default['default'].QueryParseError && err.message.startsWith("unrecognised field")) | ||
results.sort((doc1, doc2) => { | ||
return doc2.score - doc1.score; | ||
return doc2.result.score - doc1.result.score; | ||
}); | ||
const resultSet = { | ||
const realResultSet = { | ||
results: results.map((d) => { | ||
return {document: this.docStore[d.ref]}; | ||
return {type: d.type, document: this.docStore[d.result.ref]}; | ||
}) | ||
}; | ||
return Promise.resolve(resultSet); | ||
return Promise.resolve(realResultSet); | ||
} | ||
@@ -170,0 +194,0 @@ } |
@@ -10,6 +10,2 @@ import { Logger } from 'winston'; | ||
/** | ||
* The type of document to be indexed (used to name indices, to configure refresh loop, etc). | ||
*/ | ||
type: string; | ||
/** | ||
* The default interval (in seconds) that the provided collator will be called (can be overridden in config). | ||
@@ -31,7 +27,2 @@ */ | ||
decorator: DocumentDecorator; | ||
/** | ||
* (Optional) An array of document types that the given decorator should apply to. If none are provided, | ||
* the decorator will be applied to all types. | ||
*/ | ||
types?: string[]; | ||
} | ||
@@ -49,4 +40,13 @@ /** | ||
interface SearchEngine { | ||
translator: QueryTranslator; | ||
/** | ||
* Override the default translator provided by the SearchEngine. | ||
*/ | ||
setTranslator(translator: QueryTranslator): void; | ||
/** | ||
* Add the given documents to the SearchEngine index of the given type. | ||
*/ | ||
index(type: string, documents: IndexableDocument[]): void; | ||
/** | ||
* Perform a search query against the SearchEngine. | ||
*/ | ||
query(query: SearchQuery): Promise<SearchResultSet>; | ||
@@ -70,9 +70,9 @@ } | ||
*/ | ||
addCollator({ type, collator, defaultRefreshIntervalSeconds, }: RegisterCollatorParameters): void; | ||
addCollator({ collator, defaultRefreshIntervalSeconds, }: RegisterCollatorParameters): void; | ||
/** | ||
* Makes the index builder aware of a decorator. If no types are provided, it | ||
* will be applied to documents from all known collators, otherwise it will | ||
* only be applied to documents of the given types. | ||
* Makes the index builder aware of a decorator. If no types are provided on | ||
* the decorator, it will be applied to documents from all known collators, | ||
* otherwise it will only be applied to documents of the given types. | ||
*/ | ||
addDecorator({ types, decorator, }: RegisterDecoratorParameters): void; | ||
addDecorator({ decorator }: RegisterDecoratorParameters): void; | ||
/** | ||
@@ -112,2 +112,7 @@ * Compiles collators and decorators into tasks, which are added to a | ||
declare type ConcreteLunrQuery = { | ||
lunrQueryString: string; | ||
documentTypes: string[]; | ||
}; | ||
declare type LunrQueryTranslator = (query: SearchQuery) => ConcreteLunrQuery; | ||
declare class LunrSearchEngine implements SearchEngine { | ||
@@ -120,3 +125,4 @@ protected lunrIndices: Record<string, lunr.Index>; | ||
}); | ||
translator: QueryTranslator; | ||
protected translator: QueryTranslator; | ||
setTranslator(translator: LunrQueryTranslator): void; | ||
index(type: string, documents: IndexableDocument[]): void; | ||
@@ -123,0 +129,0 @@ query(query: SearchQuery): Promise<SearchResultSet>; |
{ | ||
"name": "@backstage/plugin-search-backend-node", | ||
"version": "0.1.4", | ||
"version": "0.2.0", | ||
"main": "dist/index.cjs.js", | ||
@@ -22,3 +22,3 @@ "types": "dist/index.d.ts", | ||
"dependencies": { | ||
"@backstage/search-common": "^0.1.1", | ||
"@backstage/search-common": "^0.1.2", | ||
"@types/lunr": "^2.3.3", | ||
@@ -29,4 +29,4 @@ "lunr": "^2.3.9", | ||
"devDependencies": { | ||
"@backstage/backend-common": "^0.6.3", | ||
"@backstage/cli": "^0.6.9" | ||
"@backstage/backend-common": "^0.8.2", | ||
"@backstage/cli": "^0.7.0" | ||
}, | ||
@@ -36,3 +36,3 @@ "files": [ | ||
], | ||
"gitHead": "88f56a51f68928f8da122843045d529b01832674" | ||
"gitHead": "2147ab9c1bd37e93e9e332c5ee14daa08be031e6" | ||
} |
# search-backend-node | ||
This plugin is part of a suite of plugins that comprise the Backstage search | ||
platform, which is still very much under development. This plugin specifically | ||
is responsible for: | ||
platform. This particular plugin is responsible for all aspects of the search | ||
indexing process, including: | ||
- Allowing other backend plugins to register the fact that they have documents | ||
that they'd like to be indexed by a search engine (known as `collators`) | ||
- Allowing other backend plugins to register the fact that they have metadata | ||
that they'd like to augment existing documents in the search index with | ||
(known as `decorators`) | ||
- Providing connections to search engines where actual document indices live | ||
and queries can be made. | ||
- Defining a mechanism for plugins to expose documents that they'd like to be | ||
indexed (called `collators`). | ||
- Defining a mechanism for plugins to add extra metadata to documents that the | ||
source plugin may not be aware of (known as `decorators`). | ||
- A scheduler that, at configurable intervals, compiles documents to be indexed | ||
and passes them to a search engine for indexing | ||
- Types for all of the above | ||
and passes them to a search engine for indexing. | ||
- A builder class to wire up all of the above. | ||
- Naturally, types for all of the above. | ||
@@ -16,0 +18,0 @@ Documentation on how to develop and improve the search platform is currently |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
42027
304
24