From e84f6aac87f551666342a476125a073dd4b8ce37 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 4 Jun 2021 20:35:01 +0200 Subject: [PATCH 1/3] Update @backstage/create-app to include the latest improvements in @backstage/search* Signed-off-by: Eric Peterson --- packages/create-app/package.json | 2 + packages/create-app/src/lib/versions.ts | 4 + .../app/src/components/search/SearchPage.tsx | 86 +++++++++++++++++++ .../packages/backend/package.json.hbs | 2 + .../default-app/packages/backend/src/index.ts | 3 + .../packages/backend/src/plugins/search.ts | 38 ++++++++ 6 files changed, 135 insertions(+) create mode 100644 packages/create-app/templates/default-app/packages/app/src/components/search/SearchPage.tsx create mode 100644 packages/create-app/templates/default-app/packages/backend/src/plugins/search.ts diff --git a/packages/create-app/package.json b/packages/create-app/package.json index a0fb9e6d4a..c69354044d 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -64,6 +64,8 @@ "@backstage/plugin-rollbar-backend": "*", "@backstage/plugin-scaffolder": "*", "@backstage/plugin-search": "*", + "@backstage/plugin-search-backend": "*", + "@backstage/plugin-search-backend-node": "*", "@backstage/plugin-scaffolder-backend": "*", "@backstage/plugin-tech-radar": "*", "@backstage/plugin-techdocs": "*", diff --git a/packages/create-app/src/lib/versions.ts b/packages/create-app/src/lib/versions.ts index 2a133b4bf8..a0f646747b 100644 --- a/packages/create-app/src/lib/versions.ts +++ b/packages/create-app/src/lib/versions.ts @@ -58,6 +58,8 @@ import { version as pluginRollbarBackend } from '../../../../plugins/rollbar-bac import { version as pluginScaffolder } from '../../../../plugins/scaffolder/package.json'; import { version as pluginScaffolderBackend } from '../../../../plugins/scaffolder-backend/package.json'; import { version as pluginSearch } from '../../../../plugins/search/package.json'; +import { version as pluginSearchBackend } from '../../../../plugins/search-backend/package.json'; +import { version as pluginSearchBackendNode } from '../../../../plugins/search-backend-node/package.json'; import { version as pluginTechRadar } from '../../../../plugins/tech-radar/package.json'; import { version as pluginTechdocs } from '../../../../plugins/techdocs/package.json'; import { version as pluginTechdocsBackend } from '../../../../plugins/techdocs-backend/package.json'; @@ -89,6 +91,8 @@ export const packageVersions = { '@backstage/plugin-scaffolder': pluginScaffolder, '@backstage/plugin-scaffolder-backend': pluginScaffolderBackend, '@backstage/plugin-search': pluginSearch, + '@backstage/plugin-search-backend': pluginSearchBackend, + '@backstage/plugin-search-backend-node': pluginSearchBackendNode, '@backstage/plugin-tech-radar': pluginTechRadar, '@backstage/plugin-techdocs': pluginTechdocs, '@backstage/plugin-techdocs-backend': pluginTechdocsBackend, diff --git a/packages/create-app/templates/default-app/packages/app/src/components/search/SearchPage.tsx b/packages/create-app/templates/default-app/packages/app/src/components/search/SearchPage.tsx new file mode 100644 index 0000000000..f525be80a2 --- /dev/null +++ b/packages/create-app/templates/default-app/packages/app/src/components/search/SearchPage.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; + +import { Content, Header, Page } from '@backstage/core'; +import { CatalogResultListItem } from '@backstage/plugin-catalog'; +import { + SearchBar, + SearchFilter, + SearchResult, + DefaultResultListItem, +} from '@backstage/plugin-search'; + +const useStyles = makeStyles((theme: Theme) => ({ + bar: { + padding: theme.spacing(1, 0), + }, + filters: { + padding: theme.spacing(2), + }, + filter: { + '& + &': { + marginTop: theme.spacing(2.5), + }, + }, +})); + +const SearchPage = () => { + const classes = useStyles(); + + return ( + +
+ + + + + + + + + + + + + + + + {({ results }) => ( + + {results.map(({ type, document }) => { + switch (type) { + case 'software-catalog': + return ( + + ); + default: + return ( + + ); + } + })} + + )} + + + + + + ); +}; + +export const searchPage = ; diff --git a/packages/create-app/templates/default-app/packages/backend/package.json.hbs b/packages/create-app/templates/default-app/packages/backend/package.json.hbs index 4b493a32c3..8f5a116de4 100644 --- a/packages/create-app/templates/default-app/packages/backend/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/backend/package.json.hbs @@ -27,6 +27,8 @@ "@backstage/plugin-catalog-backend": "^{{version '@backstage/plugin-catalog-backend'}}", "@backstage/plugin-proxy-backend": "^{{version '@backstage/plugin-proxy-backend'}}", "@backstage/plugin-scaffolder-backend": "^{{version '@backstage/plugin-scaffolder-backend'}}", + "@backstage/plugin-search-backend": "^{{version '@backstage/plugin-search-backend'}}", + "@backstage/plugin-search-backend-node": "^{{version '@backstage/plugin-search-backend-node'}}", "@backstage/plugin-techdocs-backend": "^{{version '@backstage/plugin-techdocs-backend'}}", "@gitbeaker/node": "^30.2.0", "@octokit/rest": "^18.5.3", diff --git a/packages/create-app/templates/default-app/packages/backend/src/index.ts b/packages/create-app/templates/default-app/packages/backend/src/index.ts index aebd034aae..3d8849fa75 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/index.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/index.ts @@ -25,6 +25,7 @@ import catalog from './plugins/catalog'; import scaffolder from './plugins/scaffolder'; import proxy from './plugins/proxy'; import techdocs from './plugins/techdocs'; +import search from './plugins/search'; import { PluginEnvironment } from './types'; function makeCreateEnv(config: Config) { @@ -57,6 +58,7 @@ async function main() { const authEnv = useHotMemoize(module, () => createEnv('auth')); const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); + const searchEnv = useHotMemoize(module, () => createEnv('search')); const appEnv = useHotMemoize(module, () => createEnv('app')); const apiRouter = Router(); @@ -65,6 +67,7 @@ async function main() { apiRouter.use('/auth', await auth(authEnv)); apiRouter.use('/techdocs', await techdocs(techdocsEnv)); apiRouter.use('/proxy', await proxy(proxyEnv)); + apiRouter.use('/search', await search(searchEnv)); apiRouter.use(notFoundHandler()); const service = createServiceBuilder(module) diff --git a/packages/create-app/templates/default-app/packages/backend/src/plugins/search.ts b/packages/create-app/templates/default-app/packages/backend/src/plugins/search.ts new file mode 100644 index 0000000000..248ed37ea8 --- /dev/null +++ b/packages/create-app/templates/default-app/packages/backend/src/plugins/search.ts @@ -0,0 +1,38 @@ +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, + }); +} From 509b5638cfde9cd9fdd4c3e0579b47da44377041 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 10 Jun 2021 11:07:41 +0200 Subject: [PATCH 2/3] Changeset Signed-off-by: Eric Peterson --- .changeset/were-only-dreaming.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/were-only-dreaming.md diff --git a/.changeset/were-only-dreaming.md b/.changeset/were-only-dreaming.md new file mode 100644 index 0000000000..6b3ebb9b54 --- /dev/null +++ b/.changeset/were-only-dreaming.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Added "out-of-the-box" alpha-milestone search features to scaffolded Backstage apps. From b7d03deeb62b0f872d97bcf8578d42e0c99f4d7e Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 10 Jun 2021 11:56:41 +0200 Subject: [PATCH 3/3] More detailed upgrade guide. Signed-off-by: Eric Peterson --- .changeset/were-only-dreaming.md | 171 +++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) diff --git a/.changeset/were-only-dreaming.md b/.changeset/were-only-dreaming.md index 6b3ebb9b54..09fd6e9875 100644 --- a/.changeset/were-only-dreaming.md +++ b/.changeset/were-only-dreaming.md @@ -3,3 +3,174 @@ --- Added "out-of-the-box" alpha-milestone search features to scaffolded Backstage apps. + +To apply this change to an existing app, do the following... + +First, navigate to your backend package and install the two new 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)); +// ... +``` + +In your frontend app package, create a new `searchPage` component at, for +example, `packages/app/src/components/search/SearchPage.tsx` with contents like +the following: + +```tsx +import React from 'react'; +import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; + +import { Content, Header, Lifecycle, Page } from '@backstage/core'; +import { CatalogResultListItem } from '@backstage/plugin-catalog'; +import { + SearchBar, + SearchFilter, + SearchResult, + DefaultResultListItem, +} from '@backstage/plugin-search'; + +const useStyles = makeStyles((theme: Theme) => ({ + bar: { + padding: theme.spacing(1, 0), + }, + filters: { + padding: theme.spacing(2), + }, + filter: { + '& + &': { + marginTop: theme.spacing(2.5), + }, + }, +})); + +const SearchPage = () => { + const classes = useStyles(); + + return ( + +
} /> + + + + + + + + + + + + + + + + {({ results }) => ( + + {results.map(({ type, document }) => { + switch (type) { + case 'software-catalog': + return ( + + ); + default: + return ( + + ); + } + })} + + )} + + + + + + ); +}; + +export const searchPage = ; +``` + +Then in `App.tsx`, import this new `searchPage` component, and set it as a +child of the existing `` route so that it looks like this: + +```diff ++import { searchPage } from './components/search/SearchPage'; +// ... +-} /> ++}> ++ {searchPage} ++; +```