본문 바로가기
WIKI 기술 지식 베이스

Search How-To 가이드

원문 보기 위키 갱신

Search How-To 가이드 (이전 프론트엔드 시스템)

이전 프론트엔드 시스템을 사용하는 Backstage 앱에서 Search를 구현·사용자 지정하는 방법들을 정리한 가이드예요.

출처: 문서

본문

:::info 이 문서는 여전히 이전 프론트엔드 시스템을 사용하는 Backstage 앱을 위한 것이에요. 앱이 새 프론트엔드 시스템을 사용한다면 현재 가이드를 읽으세요. :::

나만의 Search API 구현하는 방법

Search 플러그인은 기본적으로 하나의 주요 API 구현을 제공해요. 바로 검색 결과를 조회하기 위해 search-backend와 통신하는 SearchApi예요.

이 API를 직접 구현해야 하는 경우가 있을 수 있어요. 예를 들어 통신하고 싶은 나만의 검색 백엔드가 있을 때, API를 자신의 요구에 맞게 사용자 지정해야 할 수 있어요. 이 가이드의 목적은 두 단계로 이 과정을 안내하는 것이에요.

  • 필요에 따라 SearchApi 인터페이스를 구현해요.
export class SearchClient implements SearchApi {  // your implementation}
  • App.tsx에서 ApiFactories를 사용해 API ref인 searchApiRef를 새로 구현한 API로 재정의해요. App API에 대해 자세히 읽어보세요.
const app = createApp({  apis: [    // SearchApi    createApiFactory({      api: searchApiRef,      deps: { discovery: discoveryApiRef },      factory({ discovery }) {        return new SearchClient({ discoveryApi: discovery });      },    }),  ],});

Software Catalog 또는 TechDocs 인덱스의 필드를 사용자 지정하는 방법

때로는 catalog 콜레이터에서 어떤 데이터가 검색 인덱스로 들어가는지 제어하거나, 특정 kind에 대해 데이터를 사용자 지정하고 싶을 수 있어요. DefaultCatalogCollatorFactory에 entityTransformer 콜백을 전달하면 쉽게 처리할 수 있어요. 이 동작은 DefaultTechDocsCollatorFactory에서도 가능해요. 기본 동작을 단순히 수정하거나 완전히 새로운 문서(여전히 필요한 기본 구조를 따라야 함)를 작성할 수도 있어요.

authorization과 location은 entityTransformer로 수정할 수 없으며, location은 locationTemplate을 통해서만 수정할 수 있어요.

packages/backend/src/plugins/search.ts

const catalogEntityTransformer: CatalogCollatorEntityTransformer = (  entity: Entity,) => {  if (entity.kind === 'SomeKind') {    return {      // customize here output for 'SomeKind' kind    };  }  return {    // and customize default output    ...defaultCatalogCollatorEntityTransformer(entity),    text: 'my super cool text',  };};indexBuilder.addCollator({  collator: DefaultCatalogCollatorFactory.fromConfig(env.config, {    discovery: env.discovery,    tokenManager: env.tokenManager,    entityTransformer: catalogEntityTransformer,  }),});const techDocsEntityTransformer: TechDocsCollatorEntityTransformer = (  entity: Entity,) => {  return {    // add more fields to the index    tags: entity.metadata.tags,  };};const techDocsDocumentTransformer: TechDocsCollatorDocumentTransformer = (  doc: MkSearchIndexDoc,) => {  return {    // add more fields to the index    bost: doc.boost,  };};indexBuilder.addCollator({  collator: DefaultTechDocsCollatorFactory.fromConfig(env.config, {    discovery: env.discovery,    tokenManager: env.tokenManager,    entityTransformer: techDocsEntityTransformer,    documentTransformer: techDocsDocumentTransformer,  }),});

검색 결과 하이라이팅 스타일을 사용자 지정하는 방법

검색 결과에서 일치하는 용어의 기본 하이라이팅 스타일은 <mark> HTML 태그에 대한 브라우저 기본 스타일이에요. 하이라이트된 용어가 어떻게 보이는지 사용자 지정하려면 Backstage의 '앱 UI 사용자 지정' 가이드를 따라 원하는 스타일로 오버라이드를 만들 수 있어요.

예를 들어 새 MUI V4+V5 통합 테마(unified theming) 방법을 사용하면 다음 결과로 하이라이트된 단어가 굵게(bold) 표시되고 밑줄이 그어져요.

import {  createBaseThemeOptions,  createUnifiedTheme,  palettes,  UnifiedTheme,} from '@backstage/theme';export const myLightTheme: UnifiedTheme = createUnifiedTheme({  ...createBaseThemeOptions({    palette: palettes.light,  }),  defaultPageTheme: 'home',  components: {    /** @ts-ignore This is temporarily necessary until MUI V5 transition is completed. */    BackstageHighlightedSearchResultText: {      styleOverrides: {        highlight: {          color: 'inherit',          backgroundColor: 'inherit',          fontWeight: 'bold',          textDecoration: 'underline',        },      },    },  },});
const app : BackstageApp = createApp({  ...  themes: [{    id: 'my-light-theme',    title: 'Light Theme',    variant: 'light',    icon: <LightIcon />,    Provider: ({ children }) => (<UnifiedThemeProvider theme={myLightTheme} children={children } />)  }]});

물론 다크 테마를 원한다면 그것도 제공해야 해요.

확장을 사용해 검색 결과를 렌더링하는 방법

검색 결과용 확장은 검색 결과 항목을 렌더링하는 데 사용되는 컴포넌트를 사용자 지정할 수 있게 해줘요. 나만의 검색 결과 항목 확장을 제공하거나 플러그인 패키지가 제공하는 것을 사용할 수 있어요.

1. 플러그인 패키지에서 확장 제공하기

참고: 검색 항목 렌더러를 사용 가능하게 만들려면 plugin.provide() 함수를 사용해야 해요. 표준 MUI 테이블 등에서 목록을 렌더링하는 것과 달리, 단순히 <SearchResult /> 컴포넌트에 렌더링 함수를 제공할 수는 없어요.

아래 예시를 사용해 검색 결과 항목으로 사용될 확장을 제공할 수 있어요.

plugins/your-plugin/src/plugin.ts

import { createPlugin } from '@backstage/core-plugin-api';import { createSearchResultListItemExtension } from '@backstage/plugin-search-react';const plugin = createPlugin({ id: 'YOUR_PLUGIN_ID' });export const YourSearchResultListItemExtension = plugin.provide(  createSearchResultListItemExtension({    name: 'YourSearchResultListItem',    component: () =>      import('./components').then(m => m.YourSearchResultListItem),  }),);

목록 항목이 props를 받는다면 SearchResultListItemExtensionProps를 컴포넌트 특정 props로 확장할 수 있어요.

export const YourSearchResultListItemExtension: (  props: SearchResultListItemExtensionProps<YourSearchResultListItemProps>,) => JSX.Element | null = plugin.provide(  createSearchResultListItemExtension({    name: 'YourSearchResultListItem',    component: () =>      import('./components').then(m => m.YourSearchResultListItem),  }),);

추가로, 결과를 받아 자신의 확장이 그 결과를 렌더링하는 데 사용되어야 하는지 여부를 반환하는 predicate 함수를 정의할 수 있어요.

plugins/your-plugin/src/plugin.ts

import { createPlugin } from '@backstage/core-plugin-api';import { createSearchResultListItemExtension } from '@backstage/plugin-search-react';const plugin = createPlugin({ id: 'YOUR_PLUGIN_ID' });export const YourSearchResultListItemExtension = plugin.provide(  createSearchResultListItemExtension({    name: 'YourSearchResultListItem',    component: () =>      import('./components').then(m => m.YourSearchResultListItem),    // Only results matching your type will be rendered by this extension    predicate: result => result.type === 'YOUR_RESULT_TYPE',  }),);

앱 안에서 사용할 수 있도록 플러그인의 index.ts를 통해 새 확장을 내보내는 것을 잊지 마세요.

plugins/your-plugin/src/index.ts

export { YourSearchResultListItem } from './plugin.ts';

자세한 내용은 createSearchResultListItemExtension API reference를 참고하세요.

2. SearchPage에서 사용자 지정 검색 결과 확장

plugin.provide() 함수로 항목 렌더러를 노출했다면, 이제 기본 검색 항목 렌더러를 재정의하고 <SearchResult> 컴포넌트에 어떤 렌더러를 사용할지 알려줄 수 있어요. 렌더러의 순서가 중요하다는 점에 주의하세요! predicate 함수를 통해 일치하는 첫 번째 것이 사용돼요.

SearchPage를 사용자 지정하는 예시는 다음과 같아요.

packages/app/src/components/searchPage.tsx

import { Grid, Paper } from '@material-ui/core';import BuildIcon from '@material-ui/icons/Build';import {  Page,  Header,  Content,  DocsIcon,  CatalogIcon,} from '@backstage/core-components';import { SearchBar, SearchResult } from '@backstage/plugin-search-react';// Your search result item extensionimport { YourSearchResultListItem } from '@backstage/your-plugin';// Extensions provided by other plugin developersimport { ToolSearchResultListItem } from '@backstage/plugin-explore';import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs';import { CatalogSearchResultListItem } from '@internal/plugin-catalog-customized';// This example omits other components, like filter and paginationconst SearchPage = () => (  <Page themeId="home">    <Header title="Search" />    <Content>      <Grid container direction="row">        <Grid item xs={12}>          <Paper>            <SearchBar />          </Paper>        </Grid>        <Grid item xs={12}>          <SearchResult>            <YourSearchResultListItem />            <CatalogSearchResultListItem icon={<CatalogIcon />} />            <TechDocsSearchResultListItem icon={<DocsIcon />} />            <ToolSearchResultListItem icon={<BuildIcon />} />          </SearchResult>        </Grid>      </Grid>    </Content>  </Page>);export const searchPage = <SearchPage />;

중요: 기본 결과 항목 확장(predicate가 없는 것)은 마지막 자식으로 배치해야 다른 확장이 렌더링하는 결과와 일치하지 않을 때만 사용될 수 있어요. 기본이 아닌 확장이 지정되면 DefaultResultListItem 컴포넌트가 사용돼요.

2. SidebarSearchModal에서 사용자 지정 검색 결과 확장

SidebarSearchModal 컴포넌트를 사용하고 있을 수 있어요. 이 경우 이 컴포넌트에서 검색 항목을 다음과 같이 사용자 지정할 수 있어요.

packages/app/src/components/Root/Root.tsx

import { SidebarSearchModal } from '@backstage/plugin-search';...export const Root = ({ children }: PropsWithChildren<{}>) => {  const styles = useStyles();  return <SidebarPage>    <Sidebar>      ...      <SidebarSearchModal resultItemComponents={[        /* Provide a custom Extension search item renderer */        <CustomSearchResultListItem icon={<CatalogIcon />} />,        /* Provide an existing search item renderer */        <TechDocsSearchResultListItem icon={<DocsIcon />} />      ]} />      ...    </Sidebar>    {children}  </SidebarPage>;};

3. 사용자 지정 SearchModal에서 사용자 지정 검색 결과 확장

SearchModal을 완전히 사용자 지정했다고 가정하고, 확장으로 결과를 렌더링하는 예시는 다음과 같아요.

packages/app/src/components/searchModal.tsx

import { DialogContent, DialogTitle, Paper } from '@material-ui/core';import BuildIcon from '@material-ui/icons/Build';import { DocsIcon, CatalogIcon } from '@backstage/core-components';import { SearchBar, SearchResult } from '@backstage/plugin-search-react';// Your search result item extensionimport { YourSearchResultListItem } from '@backstage/your-plugin';// Extensions provided by other plugin developersimport { ToolSearchResultListItem } from '@backstage/plugin-explore';import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs';import { CatalogSearchResultListItem } from '@internal/plugin-catalog-customized';export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => (  <>    <DialogTitle>      <Paper>        <SearchBar />      </Paper>    </DialogTitle>    <DialogContent>      <SearchResult onClick={toggleModal}>        <CatalogSearchResultListItem icon={<CatalogIcon />} />        <TechDocsSearchResultListItem icon={<DocsIcon />} />        <ToolSearchResultListItem icon={<BuildIcon />} />        {/* As a "default" extension, it does not define a predicate function,        so it must be the last child to render results that do not match the above extensions */}        <YourSearchResultListItem />      </SearchResult>    </DialogContent>  </>);

결과 항목 확장을 받아들이는 더 구체적인 검색 결과 레이아웃 컴포넌트도 있어요. 해당 문서를 확인하세요: SearchResultList와 SearchResultGroup.

더 알아보기 (Learn more)