Skip to main content

将搜索功能集成到插件中

Backstage 搜索平台旨在为插件开发人员提供在其插件中提供搜索体验所需的应用程序接口和接口,同时抽象出(转而授权应用程序集成商选择)具体的底层搜索技术。

在本页中,您将找到在插件中利用Backstage搜索平台的概念和教程。

向搜索平台提供数据

创建一个整理器

了解什么是 collator将有助于您构建它。

想象一下,您有一个负责在数据库中存储常见问题片段的插件。 您希望其他工程师能够轻松找到您的问题和答案。 因此,您希望搜索平台对它们进行索引。 假设常见问题片段可以通过以下 URL 查看backstage.example.biz/faq-snippets.

搜索平台提供了一个界面(DocumentCollatorFactory从包装@backstage/plugin-search-common它的工作原理是将每个条目注册为一个 "文档",之后每个文档代表一个搜索结果。

如果您不确定或想遵循最佳实践,可以随时查看工作示例,例如 StackOverflowQuestionsCollatorFactory

1. 安装拼版器接口依赖项

我们需要接口DocumentCollatorFactory从包装@backstage/plugin-search-common因此,让我们把它添加到插件依赖项中:

# navigate to the plugin directory
# (for this tutorial our plugin lives in the backstage repo, if your plugin lives in a separate repo you need to clone that first)
cd plugins/faq-snippets

# Create a new branch using Git command-line
git checkout -b tutorials/new-faq-snippets-collator

# Install the package containing the interface
yarn add @backstage/plugin-search-common

2. 确定文件类型

在开始根据常见问题条目生成文档之前,我们首先要定义一种文档类型,其中包含所有必要信息,以便在搜索结果中显示我们的条目。 软件包@backstage/plugin-search-common包含一个IndexableDocument我们可以扩展。

创建新文件plugins/faq-snippets/src/search/collators/FaqSnippetDocument.ts并粘贴以下内容:

import { IndexableDocument } from '@backstage/plugin-search-common';

export interface FaqSnippetDocument extends IndexableDocument {
answered_by: string;
}

3. 使用Backstage应用程序配置

您的新拼版机可以直接使用Backstage的配置,从而受益匪浅。app-config.yaml文件,该文件位于项目的根文件夹中:

faq:
baseUrl: https://backstage.example.biz/faq-snippets

4. 实施您的拼版机

试想一下,您的常见问题可以在 URL 中检索到https://backstage.example.biz/faq-snippets采用以下 JSON 响应格式:

{
"items": [
{
"id": 42,
"question": "What is The Answer to the Ultimate Question of Life, the Universe, and Everything?",
"answer": "Forty-two",
"user": "Deep Thought"
}
]
}

下面我们提供一个示例,说明常见问题整理器工厂如何使用我们的新文档类型,并将其放置在plugins/faq-snippets/src/search/collators/FaqCollatorFactory.ts锉刀

import fetch from 'cross-fetch';
import { Logger } from 'winston';
import { Config } from '@backstage/config';
import { Readable } from 'stream';
import { DocumentCollatorFactory } from '@backstage/plugin-search-common';

import { FaqDocument } from './FaqDocument';

export type FaqCollatorFactoryOptions = {
baseUrl?: string;
logger: Logger;
};

export class FaqCollatorFactory implements DocumentCollatorFactory {
private readonly baseUrl: string | undefined;
private readonly logger: Logger;
public readonly type: string = 'faq-snippets';

private constructor(options: FaqCollatorFactoryOptions) {
this.baseUrl = options.baseUrl;
this.logger = options.logger;
}

static fromConfig(config: Config, options: FaqCollatorFactoryOptions) {
const baseUrl =
config.getOptionalString('faq.baseUrl') ||
'https://backstage.example.biz/faq-snippets';
return new FaqCollatorFactory({ ..options, baseUrl });
}

async getCollator() {
return Readable.from(this.execute());
}

async *execute(): AsyncGenerator<FaqDocument> {
if (!this.baseUrl) {
this.logger.error(`No faq.baseUrl configured in your app-config.yaml`);
return;
}

const response = await fetch(this.baseUrl);
const data = await response.json();

for (const faq of data.items) {
yield {
title: faq.question,
location: `/faq-snippets/${faq.id}`,
text: faq.answer,
answered_by: faq.user,
};
}
}
}

5. 测试拼版机

为验证您的实现是否按预期运行,请务必为其添加测试。 为方便起见,您可以使用测试管道实用程序,它可以模拟一个流水线,您可以将自定义拼版器集成到该流水线中。

看看DefaultTechDocsCollatorFactory 测试例如

6. 让他人可以发现您的插件整理器

如果您想让其他采用者发现您的校对工具,请将其添加到集成到搜索的插件.

在插件中加入搜索体验

虽然核心搜索插件提供了组件和扩展功能,使应用程序集成商能够构建全局搜索体验,但您可能会发现,您希望在插件内获得范围更窄的搜索体验。 这可以是一个自动完成式搜索栏,专注于插件提供的文档(例如,..技术文档搜索组件),或抽象为显示与页面上其他内容上下文相关的链接列表的 widget。

搜索体验概念

了解这些高级概念将有助于您设计插件内搜索体验。

  • All search experiences must be wrapped in a <SearchContextProvider>, which is provided by @backstage/plugin-search-react. This context keeps track of state necessary to perform search queries and display any results. As inputs to the query are updated (e.g. a term or filter values), the updated query is executed and results are refreshed. Check out the SearchContextValue for details. * The aforementioned state can be modified and/or consumed via the useSearch() hook, also exported by @backstage/plugin-search-react. * For more literal search experiences, reusable components are available to import and compose into a cohesive experience in your plugin (e.g. <SearchBar /> or <SearchFilter.Checkbox />). You can see all such components in Backstage's storybook.

搜索体验教程

以下教程使用了一些软件包和插件,您可能还没有将它们作为插件的依赖项;请务必在使用前添加它们!

  • @backstage/plugin-search-react - A package containing components, hooks, and types that are shared across all frontend plugins, including plugins like yours! * @backstage/plugin-search - The main search plugin, used by app integrators to compose global search experiences. * @backstage/core-components - A package containing generic components useful for a variety of experiences built in Backstage.

改进 "404 "页面体验

想象一下,您有一个插件,允许用户管理小工具.也许可以通过以下 URL 查看它们backstage.example.biz/widgets/{widgetName}在某些情况下,小工具会被重命名,聊天系统、维基或浏览器书签中指向该小工具页面的链接也会过时,从而导致错误或 404。

如果显示的不是一个已损坏的页面或通用的 "好像有人掉了麦克风 "的 404 页面,而是一个可能相关的小部件列表,会怎么样呢?

import { Link } from '@backstage/core-components';
import { SearchResult } from '@backstage/plugin-search';
import { SearchContextProvider } from '@backstage/plugin-search-react';

export const Widget404Page = ({ widgetName }) => {
// Supplying this to <SearchContextProvider> runs a pre-filtered search with
// the given widgetName as the search term, focused on search result of type
// "widget" with no other filters.
const preFiltered = {
term: widgetName,
types: ['widget'],
filters: {},
};

return (
<SearchContextProvider initialState={preFiltered}>
{/* The <SearchResult> component allows us to iterate through results and
display them in whatever way fits best! */}
<SearchResult>
{({ results }) => (
{results.map(({ document }) => (
<Link to={document.location} key={document.location}>
{document.title}
</Link>
))}
)}
<SearchResult>
</SearchContextProvider>
);
);

并非所有的搜索体验都需要用户输入!正如你所看到的,利用Backstage搜索平台的前端框架,不一定要给用户输入控制。

简单搜索页面

当然,您也可以在插件中提供功能更全面的搜索体验。 最简单的方法是利用由@backstage/plugin-search包,就像这样:

import { useProfile } from '@internal/api';
import {
Content,
ContentHeader,
PageWithHeader,
} from '@backstage/core-components';
import { SearchBar, SearchResult } from '@backstage/plugin-search';
import { SearchContextProvider } from '@backstage/plugin-search-react';

export const ManageMyWidgets = () => {
const { primaryTeam } = useProfile();
// In this example, note how we are pre-filtering results down to a specific
// owner field value (the currently logged-in user's team), but allowing the
// search term to be controlled by the user via the <SearchBar /> component.
const preFiltered = {
types: ['widget'],
term: '',
filters: {
owner: primaryTeam,
},
};

return (
<PageWithHeader title="Widgets Home">
<Content>
<ContentHeader title="All your Widgets and More" />
<SearchContextProvider initialState={preFiltered}>
<SearchBar />
<SearchResult>
{/* Render results here, just like above */}
</SearchResult>
</SearchContextProvider>
</Content>
</PageWithHeader>
);
};

定制搜索控制面

如果@backstage/plugin-search如果您觉得自己的组件不够用,没问题!我们提供了一个应用程序接口,您可以使用它来编写自己的组件,以控制搜索上下文的各个部分。

import { useSearch } from '@backstage/plugin-search-react';
import ChipInput from 'material-ui-chip-input';

export const CustomChipFilter = ({ name }) => {
const { filters, setFilters } = useSearch();
const chipValues = filters[name] || [];

// When a chip value is changed, update the filters value by calling the
// setFilters function from the search context.
const handleChipChange = (chip, index) => {
// There may be filters set for other fields. Be sure to maintain them.
setFilters(prevState => {
const { [name]: filter = [], ..others } = prevState;

if (index === undefined) {
filter.push(chip);
} else {
filter.splice(index, 1);
}

return { ..others, [name]: filter };
});
};

return (
<ChipInput
value={chipValues}
onAdd={handleChipChange}
onDelete={handleChipChange}
/>
);
};

查看SearchContextValue 类型以获取更多关于操作和读取搜索上下文的可用方法和值的详细信息。

如果您制作了一些通用和可重复使用的组件,请考虑向上游贡献您的组件,这样Backstage搜索平台的所有用户都能从中受益。 欢迎提出问题和拉取请求。

自定义搜索结果

整个 Backstage 的搜索结果都是以列表的形式呈现的,因此可以很容易地对列表项进行自定义。默认结果列表项插件最适合提供自定义结果列表项,以显示只有插件才知道的相关信息。

下面的示例想象了YourCustomSearchResult作为一种搜索结果,其中包含相关的tags可在标题/正文下方显示为芯片。

import { Link } from '@backstage/core-components';
import { useAnalytics } from '@backstage/core-plugin-api';
import { ResultHighlight } from '@backstage/plugin-search-common';
import { HighlightedSearchResultText } from '@backstage/plugin-search-react';

type CustomSearchResultListItemProps = {
result: YourCustomSearchResult;
rank?: number;
highlight?: ResultHighlight;
};

export const CustomSearchResultListItem = (
props: CustomSearchResultListItemProps,
) => {
const { title, text, location, tags } = props.result;

const analytics = useAnalytics();
const handleClick = () => {
analytics.captureEvent('discover', title, {
attributes: { to: location },
value: props.rank,
});
};

return (
<Link noTrack to={location} onClick={handleClick}>
<ListItem alignItems="center">
<Box flexWrap="wrap">
<ListItemText
primaryTypographyProps={{ variant: 'h6' }}
primary={
highlight?.fields?.title ? (
<HighlightedSearchResultText
text={highlight.fields.title}
preTag={highlight.preTag}
postTag={highlight.postTag}
/>
) : (
title
)
}
secondary={
highlight?.fields?.text ? (
<HighlightedSearchResultText
text={highlight.fields.text}
preTag={highlight.preTag}
postTag={highlight.postTag}
/>
) : (
text
)
}
/>
{tags &&
tags.map((tag: string) => (
<Chip key={tag} label={`Tag: ${tag}`} size="small" />
))}
</Box>
</ListItem>
<Divider />
</Link>
);
};

可选择使用<HighlightedSearchResultText>该组件可以根据用户的搜索查询,突出显示搜索结果的相关部分。

分析注意事项搜索结果组件:为了让应用程序集成商跟踪和改进整个 Backstage 的搜索体验,他们必须了解用户何时搜索、搜索什么以及搜索后点击什么。 在提供自定义结果组件时,作为插件开发人员,您有责任根据搜索分析惯例对其进行工具化。 特别是:

  • You must use the analytics.captureEvent method, from the useAnalytics() hook (detailed plugin analytics docs are here). * You must ensure that the action of the event, representing a click on a search result item, is discover, and the subject is the title of the clicked result. In addition, the to attribute should be set to the result's location, and the value of the event must be set to the rank (passed in as a prop). * You must ensure that the aforementioned captureEvent method is called when a user clicks the link; you should further ensure that the noTrack prop is added to the link (which disables default link click tracking, in favor of this custom instrumentation).

有关自定义结果列表项的其他示例和灵感,请查看堆栈溢出搜索结果列表项目](https://github.com/backstage/backstage/blob/c981e83/plugins/stack-overflow/src/search/StackOverflowSearchResultListItem/StackOverflowSearchResultListItem.tsx)或[<目录搜索结果列表项目> 组件