Merge pull request #5715 from backstage/mob/document-search-v1

[Search] WIP documentation for the V1 Search Milestone
This commit is contained in:
Eric Peterson
2021-06-09 19:22:24 +02:00
committed by GitHub
4 changed files with 413 additions and 0 deletions
+112
View File
@@ -0,0 +1,112 @@
---
id: concepts
title: Search Concepts
description: Documentation on Backstage Search Concepts
---
# Search Concepts
Backstage Search lets you find the right information you are looking for in the
Backstage ecosystem.
To get started, you should get familiar with these core concepts:
- [Search Engines](#search-engines)
- [Query Translators](#query-translators)
- [Documents and Indices](#documents-and-indices)
- [Collators](#collators)
- [Decorators](#decorators)
- [The Scheduler](#the-scheduler)
- [The Search Page](#the-search-page)
- [Search Context and Components](#search-context-and-components)
### Search Engines
Backstage Search isn't a search engine itself, rather, it provides an interface
between your Backstage instance and a Search Engine of your choice. More
concretely, a `SearchEngine` is an interface whose concrete implementations
facilitate communication with different search engines (like ElasticSearch,
Lunr, Solr, etc). This abstraction exists in order to support your
organization's needs.
Out of the box, Backstage Search comes pre-packaged with an in-memory search
engine implementation built on top of Lunr.
### Query Translators
Because you can bring your own search engine, and because search engines have
very unique and robust query languages themselves, there needs to be a
translation layer between an abstract search query (containing search terms,
filters, and document types) into a concrete search query that is specific to a
search engine.
Search Engines come pre-packaged with simple translators that do rudimentary
transformations of search terms and filters, but you may want to provide your
own to help tune search results in the context of your organization.
### Documents and Indices
"Document" is an abstract concept representing something that can be found by
searching for it. A document can represent a software entity, a TechDocs page,
etc. Documents are made up of metadata fields, at a minimum including a title,
text, and location (as in a URL).
An index is a collection of such documents of a given type.
### Collators
You need to be able to search something! Collators are the way to define what
can be searched. Specifically, they're classes which return documents conforming
to a minimum set of fields (including a document title, location, and text), but
which can contain any other fields as defined by the collator itself. One
collator is responsible for defining and collecting documents of a type.
Some plugins, like the Catalog Backend, provide so-called "default" collators
which you can use out-of-the-box to start searching across Backstage quickly.
### Decorators
Sometimes you want to add extra information to a set of documents in your search
index that the collator may not be aware of. For example, the Software Catalog
knows about software entities, but it may not know about their usage or quality.
Decorators are classes which can add extra fields to pre-collated documents.
This extra metadata could then be used to bias search results or otherwise
improve the search experience in your Backstage instance.
### The Scheduler
There are many ways a search index could be built and maintained, but Backstage
Search chooses to completely rebuild indices on a schedule. Different collators
can be configured to refresh at different intervals, depending on how often the
source information is updated.
### The Search Page
Search pages are very custom things. Not every Backstage instance will want the
same interface! In order to allow you to customize your search experience to
your heart's content, the Search Plugin takes care of state management and other
search logic for you, but most of the layout of a search page lives in a search
page component defined in your Backstage App.
For an example of a simple search page, check
[getting started](./getting-started.md#adding-search-to-the-frontend)
### Search Context and Components
A search experience, like a page, is composed of any number of search
components, which are all wired up using a search context.
Each search experience's context consists of details like a search term,
filters, types, results, and a page cursor for handling pagination. Different
components use this context in different ways. For example, the `<SearchBar />`
can set the search term, `<SearchFilter />` components can set filters, and
search results can be displayed using the `<SearchResult />` component.
The `<SearchResult />` and `<SearchFilter />` components are special, in that
they themselves are extensible. For an example of how to extend these
components, check
[getting started](./getting-started.md#adding-search-to-the-frontend).
If you need even more customization, you can use the search context like any
other React context to create custom search components of your own.
+297
View File
@@ -0,0 +1,297 @@
---
id: getting-started
title: Getting Started with Search
description: How to set up and install Backstage Search
---
# Getting Started
Search functions as a plugin to Backstage, so you will need to use Backstage to
use Search.
If you haven't setup Backstage already, start
[here](../../getting-started/index.md).
> If you used `npx @backstage/create-app`, and you have a search page defined in
> `packages/app/src/components/search`, skip to
> [`Customizing Search`](#customizing-search) below.
## Adding Search to the Frontend
```bash
# From your Backstage root directory
cd packages/app
yarn add @backstage/plugin-search
```
Create a new `packages/app/src/components/search/SearchPage.tsx` file in your
Backstage app with the following contents:
```tsx
import React from 'react';
import { Content, Header, Page } from '@backstage/core';
import { Grid, List, Card, CardContent } from '@material-ui/core';
import {
SearchBar,
SearchResult,
DefaultResultListItem,
SearchFilter,
} from '@backstage/plugin-search';
import { CatalogResultListItem } from '@backstage/plugin-catalog';
export const searchPage = (
<Page themeId="home">
<Header title="Search" />
<Content>
<Grid container direction="row">
<Grid item xs={12}>
<SearchBar />
</Grid>
<Grid item xs={3}>
<Card>
<CardContent>
<SearchFilter.Select
name="kind"
values={['Component', 'Template']}
/>
</CardContent>
<CardContent>
<SearchFilter.Checkbox
name="lifecycle"
values={['experimental', 'production']}
/>
</CardContent>
</Card>
</Grid>
<Grid item xs={9}>
<SearchResult>
{({ results }) => (
<List>
{results.map(result => {
switch (result.type) {
case 'software-catalog':
return (
<CatalogResultListItem
key={result.document.location}
result={result.document}
/>
);
default:
return (
<DefaultResultListItem
key={result.document.location}
result={result.document}
/>
);
}
})}
</List>
)}
</SearchResult>
</Grid>
</Grid>
</Content>
</Page>
);
```
Bind the above Search Page to the `/search` route in your
`packages/app/src/App.tsx` file, like this:
```tsx
import { SearchPage } from '@backstage/plugin-search';
import { searchPage } from './components/search/SearchPage';
const routes = (
<FlatRoutes>
<Route path="/search" element={<SearchPage />}>
{searchPage}
</Route>
</FlatRoutes>
);
```
## Adding Search to the Backend
Add the following plugins into your backend app:
```bash
# From your Backstage root directory
cd packages/backend
yarn add @backstage/plugin-search-backend @backstage/plugin-search-backend-node
```
Create a `packages/backend/src/plugins/search.ts` file containing the following
code:
```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) {
const searchEngine = new LunrSearchEngine({ logger });
const indexBuilder = new IndexBuilder({ logger, searchEngine });
indexBuilder.addCollator({
type: 'software-catalog',
defaultRefreshIntervalSeconds: 600,
collator: new DefaultCatalogCollator({ discovery }),
});
const { scheduler } = await indexBuilder.build();
scheduler.start();
useHotCleanup(module, () => scheduler.stop());
return await createRouter({
engine: indexBuilder.getSearchEngine(),
logger,
});
}
```
Make the following modifications to your `packages/backend/src/index.ts` file:
Import the `plugins/search` file you created above:
```typescript
import search from './plugins/search';
```
Set up an environment for search:
```typescript
const searchEnv = useHotMemoize(module, () => createEnv('search'));
```
Register the search service with the router:
```typescript
apiRouter.use('/search', await search(searchEnv));
```
## Customizing Search
### Frontend
The Search Plugin exposes several default filter types as static properties,
including `<SearchFilter.Select />` and `<SearchFilter.Checkbox />`. These allow
you to provide values relevant to your Backstage instance that, when selected,
get passed to the backend.
```tsx {2-5,8-11}
<CardContent>
<SearchFilter.Select
name="kind"
values={['Component', 'Template']}
/>
</CardContent>
<CardContent>
<SearchFilter.Checkbox
name="lifecycle"
values={['production', 'experimental']}
/>
</CardContent>
```
If you have advanced filter needs, you can specify your own filter component
like this (although new core filter contributions are welcome):
```tsx
import { useSearch, SearchFilter } from '@backstage/plugin-search';
const MyCustomFilter = () => {
// Note: filters contain filter data from other filter components. Be sure
// not to clobber other filters' data!
const { filters, setFilters } = useSearch();
return (/* ... */);
};
// Which could be rendered like this:
<SearchFilter component={MyCustomFilter} />
```
It's good practice for search results to highlight information that was used to
return it in the first place! The code below highlights how you might specify a
custom result item component, using the `<CatalogResultListItem />` component as
an example:
```tsx {7-13}
<SearchResult>
{({ results }) => (
<List>
{results.map(result => {
// result.type is the index type defined by the collator.
switch (result.type) {
case 'software-catalog':
return (
<CatalogResultListItem
key={result.document.location}
result={result.document}
/>
);
// ...
}
})}
</List>
)}
</SearchResult>
```
### Backend
Backstage Search isn't a search engine itself, rather, it provides an interface
between your Backstage instance and a
[Search Engine](./concepts.md#search-engines) of your choice. Currently, we only
support one, an in-memory search Engine called Lunr. It can be instantiated like
this:
```typescript
const searchEngine = new LunrSearchEngine({ logger });
const indexBuilder = new IndexBuilder({ logger, searchEngine });
```
Backstage Search can be used to power search of anything! Plugins like the
Catalog offer default [collators](./concepts.md#collators) which are responsible
for providing documents [to be indexed](./concepts.md#documents-and-indices).
You can register any number of collators with the `IndexBuilder` like this:
```typescript
const indexBuilder = new IndexBuilder({ logger, searchEngine });
indexBuilder.addCollator({
type: 'software-catalog',
defaultRefreshIntervalSeconds: 600,
collator: new DefaultCatalogCollator({ discovery }),
});
indexBuilder.addCollator({
type: 'my-custom-stuff',
defaultRefreshIntervalSeconds: 3600,
collator: new MyCustomCollator(),
});
```
Backstage Search builds and maintains its index
[on a schedule](./concepts.md#the-scheduler). You can change how often the
indexes are rebuilt for a given type of document. You may want to do this if
your documents are updated more or less frequently. You can do so by modifying
its `defaultRefreshIntervalSeconds` value, like this:
```typescript {3}
indexBuilder.addCollator({
type: 'software-catalog',
defaultRefreshIntervalSeconds: 600,
collator: new DefaultCatalogCollator({ discovery }),
});
```