diff --git a/.changeset/fair-coins-deny.md b/.changeset/fair-coins-deny.md new file mode 100644 index 0000000000..8b9fc871eb --- /dev/null +++ b/.changeset/fair-coins-deny.md @@ -0,0 +1,10 @@ +--- +'@backstage/plugin-search': major +'@backstage/plugin-search-react': major +'@backstage/plugin-search-backend': major +'@backstage/plugin-search-backend-node': major +'@backstage/plugin-search-common': major +'@backstage/plugin-search-backend-module-elasticsearch': major +--- + +This package has been promoted to 1.0. Read more about what it means in [New release: Backstage Search 1.0 blog](https://backstage.io/blog/2022/07/19/releasing-backstage-search-1.0) diff --git a/.changeset/spotty-balloons-stare.md b/.changeset/spotty-balloons-stare.md new file mode 100644 index 0000000000..9e75ae9cbe --- /dev/null +++ b/.changeset/spotty-balloons-stare.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-pg': patch +--- + +Now imports `SearchEngine` interface from `@backstage/plugin-search-common` instead of `@backstage/plugin-search-backend-node` diff --git a/docs/features/search/README.md b/docs/features/search/README.md index f1744d4d80..618231a31e 100644 --- a/docs/features/search/README.md +++ b/docs/features/search/README.md @@ -14,60 +14,16 @@ Backstage Search lets you find the right information you are looking for in the ## Features -- A federated, faceted search, searching across all entities registered in your Backstage instance. +- A search that lets you bring your own search engine. +- A search that lets you extend it by creating collators for easily indexing content from plugins and other sources. +- A search that lets you create composable search page experiences. +- A search that lets you customize the look and feel of each search result. -- A search that lets you plug in your own search engine of choice. - -- A standardized search API where you can choose to index data from other plugins. - -## Project roadmap - -### Now - -**Backstage Search 1.0** - -A stable Search API for plugin developers to add search to their plugins, and app integrators to expose that to their users. - -We will consider Backstage Search to be 1.0 when the above -use-cases are met, and an ecosystem of search-enabled plugins are available and -stable. - -- As a plugin developer, there should be at least one example of a Backstage - plugin that integrates with search that I can use as inspiration for my own - plugin's search capabilities (for example, the TechDocs plugin). -- As an app integrator, there should be plenty of examples and documentation on - how to customize and extend search in my Backstage instance to meet my - organization's needs. - -### Next - -_Not specified_ - -### Someday/Maybe - -_Not specified_ - -### Done - -See [Done](#done) below for a list of completed roadmap items. +See the more detailed [architecture](./architecture.md) and [tech stack](./architecture.md#tech-stack). ## Supported -The following sections show the search engines and plugins currently supported by Backstage Search. - -### Search engines - -See [Backstage Search Architecture](architecture.md) to get an overview of how -the search engines are used. - -| Search Engines | Support Status | -| -------------------------------------------------- | -------------- | -| [Lunr](./search-engines.md#lunr) | ✅ | -| [ElasticSearch](./search-engines.md#elasticsearch) | ✅ | -| [Postgres](./search-engines.md#postgres) | ✅ | - -[Reach out to us](#get-involved) if you want to chat about support for more -search engines. +The following sections show the plugins and search engines currently supported by Backstage Search. ### Plugins integrated with Backstage Search @@ -77,20 +33,19 @@ search engines. | [TechDocs](./how-to-guides.md#how-to-index-techdocs-documents) | ✅ | | [Stack Overflow](https://github.com/backstage/backstage/blob/master/plugins/stack-overflow-backend/README.md#index-stack-overflow-questions-to-search) | ✅ | -[Reach out to us](#get-involved) if you want to chat about support for more -plugins integrated to search. +### Search engines -## Tech Stack +See [Backstage Search Architecture](architecture.md) to get an overview of how +the search engines are used. -| Stack | Location | -| ------------------------- | ----------------------------------------------------- | -| Frontend Plugin | @backstage/plugin-search | -| Frontend Plugin Library | @backstage/plugin-search-react | -| Isomorphic Plugin Library | @backstage/plugin-search-common | -| Backend Plugin | @backstage/plugin-search-backend | -| Backend Plugin Library | @backstage/plugin-search-backend-node | -| Backend Plugin Module | @backstage/plugin-search-backend-module-elasticsearch | -| Backend Plugin Module | @backstage/plugin-search-backend-module-pg | +| Search Engines | Support Status | +| -------------------------------------------------- | -------------- | +| [ElasticSearch](./search-engines.md#elasticsearch) | ✅ | +| [Lunr](./search-engines.md#lunr) | ✅ | +| [Postgres](./search-engines.md#postgres) | Community ✅ | + +[Reach out to us](#get-involved) if you want to chat about support for more plugin integrations and +search engines. ## Get involved @@ -98,9 +53,9 @@ For any questions, feedback, or to help move search forward, reach out to us in the **#search** channel of our [Discord chatroom](https://github.com/backstage/backstage#community). -## Done +## Use Cases supported -**Backstage Search Pre-Alpha** +#### **Backstage Search Pre-Alpha** Search Frontend letting you search through the entities of the software catalog. @@ -118,7 +73,7 @@ there by means of a front-end only, non-extensible MVP. - As a software engineer I should be able to hide the filters if I don’t need to use them. -**Backstage Search Alpha** +#### **Backstage Search Alpha** Basic “out-of-the-box” in-memory indexing process of entities, and their metadata, registered to the Software Catalog. @@ -136,7 +91,7 @@ met, but built on top of a flexible, extensible platform. should be possible to have the pre-alpha user experiences covered without having to set up and configure a search engine. -**Backstage Search Beta** +#### **Backstage Search Beta** At least one production-ready search engine that supports the same use-cases as in the alpha. @@ -151,3 +106,18 @@ are met, and can be deployed using a production-ready search engine. - As an integrator, I should be able to tune the queries sent to my chosen search engine according to my organization's needs, but a sensible default query should be in place so that I am not required to do so. + +#### **Backstage Search 1.0** + +A stable Search API for plugin developers to add search to their plugins, and app integrators to expose that to their users. + +We will consider Backstage Search to be 1.0 when the above +use-cases are met, and an ecosystem of search-enabled plugins are available and +stable. + +- As a plugin developer, there should be at least one example of a Backstage + plugin that integrates with search that I can use as inspiration for my own + plugin's search capabilities (for example, the TechDocs plugin). +- As an app integrator, there should be plenty of examples and documentation on + how to customize and extend search in my Backstage instance to meet my + organization's needs. diff --git a/docs/features/search/architecture.md b/docs/features/search/architecture.md index 4fac0e7cff..f1183cec5f 100644 --- a/docs/features/search/architecture.md +++ b/docs/features/search/architecture.md @@ -4,10 +4,6 @@ title: Search Architecture description: Documentation on Search Architecture --- -> _This architecture has not been fully implemented yet. Find our milestones to -> follow our progress and help contribute on the -> [Search Roadmap](./README.md#project-roadmap)._ - Below you can explore the Search Architecture. Our aim with this architecture is to support a wide variety of search engines, while providing a simple developer experience for plugin developers, and a good out-of-the-box experience for @@ -44,3 +40,15 @@ Architecture non-goals: - At this time, we do not intend to directly support event-driven or incremental index management. Instead, we'll be focused on scheduled, bulk index management. + +## Tech Stack + +| Stack | Location | +| ------------------------- | ----------------------------------------------------- | +| Frontend Plugin | @backstage/plugin-search | +| Frontend Plugin Library | @backstage/plugin-search-react | +| Isomorphic Plugin Library | @backstage/plugin-search-common | +| Backend Plugin | @backstage/plugin-search-backend | +| Backend Plugin Library | @backstage/plugin-search-backend-node | +| Backend Plugin Module | @backstage/plugin-search-backend-module-elasticsearch | +| Backend Plugin Module | @backstage/plugin-search-backend-module-pg | diff --git a/docs/features/search/how-to-guides.md b/docs/features/search/how-to-guides.md index fb0883d506..01d85b8aa2 100644 --- a/docs/features/search/how-to-guides.md +++ b/docs/features/search/how-to-guides.md @@ -148,271 +148,6 @@ indexBuilder.addCollator({ As shown above, you can add a catalog entity filter to narrow down what catalog entities are indexed by the search engine. -## How to migrate from Search Alpha to Beta - -For the purposes of this guide, Search Beta version is defined as: - -- **Search Plugin**: At least `v0.7.2` -- **Search Backend Plugin**: At least `v0.4.6` -- **Search Backend Node**: At least `v0.5.0` -- **Search Common**: At least `v0.3.0` - -In the Beta version, the Search Platform's indexing process has been rewritten -as a stream pipeline in order to improve efficiency and performance on large -sets of documents. - -If you've not yet extended the Search Platform with custom code, and have -instead taken advantage of default collators, decorators, and search engines -provided by existing plugins, the migration process is fairly straightforward: - -1. Upgrade to at least version `0.5.0` of - `@backstage/plugin-search-backend-node`, as well as any backend plugins whose - collators you are using (e.g. at least version `0.23.0` of - `@backstage/plugin-catalog-backend` and/or version `0.14.1` of - `@backstage/plugin-techdocs-backend`), as well as any search-engine specific - plugin you are using (e.g. at least version `0.3.0` of - `@backstage/plugin-search-backend-module-pg` or version `0.1.0` of - `@backstage/plugin-search-backend-module-elasticsearch`). -2. Then, make the following changes to your - `/packages/backend/src/plugins/search.ts` file: - - ```diff - -import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; - -import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; - +import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; - +import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; - // ... - const indexBuilder = new IndexBuilder({ logger: env.logger, searchEngine }); - indexBuilder.addCollator({ - defaultRefreshIntervalSeconds: 600, - - collator: DefaultCatalogCollator.fromConfig(env.config, { - discovery: env.discovery, - }), - + factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { - discovery: env.discovery, - }), - }); - indexBuilder.addCollator({ - defaultRefreshIntervalSeconds: 600, - - collator: DefaultTechDocsCollator.fromConfig(env.config, { - + factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { - discovery: env.discovery, - logger: env.logger, - }), - }); - ``` - -Any custom collators, decorators, or search engine implementations will require -minor refactoring. Continue on for details. - -### Rewriting alpha-style collators for beta - -In alpha versions of the Backstage Search Platform, collators were classes that -implemented an `execute` method which resolved an `IndexableDocument` array. - -In beta versions, the logic encapsulated by the aforementioned `execute` method -is contained within an [object-mode][obj-mode] `Readable` stream where each -object pushed onto the stream is of type `IndexableDocument`. Instances of this -stream are instantiated by a factory class conforming to the -`DocumentCollatorFactory` interface. - -The optimal conversion strategy will vary depending on the collator's logic, but -the simplest conversion can follow a process like this: - -1. Rename your collator class to something like `YourCollatorFactory` and update - it to implement `DocumentCollatorFactory` instead of `DocumentCollator`. -2. Update its `execute` method so that it resolves - `AsyncGenerator` instead of `YourIndexableDocument[]`. -3. Implement `DocumentCollatorFactory`'s `getCollator` method which resolves to - `Readable.from(this.execute())` (which is a utility for creating [readable - streams][read-stream] from [async generators][async-gen]). - -```ts -import { DocumentCollatorFactory } from '@backstage/plugin-search-backend-node'; -import { Readable } from 'stream'; -export class YourCollatorFactory implements DocumentCollatorFactory { - public readonly type: string = 'your-type'; - async *execute(): AsyncGenerator { - const widgets = await this.client.getWidgets(); - for (const widget of widgets) { - yield { - title: widget.name, - location: widget.url, - text: widget.description, - }; - } - } - async getCollator() { - return Readable.from(this.execute()); - } -} -``` - -Note: it may be possible to simplify your collator dramatically! If your custom -collator was previously using streams under the hood (for example, by reading -newline delimited JSON from a local or remote file), you could just expose the -stream directly via a simple factory class: - -```ts -import { DocumentCollatorFactory } from '@backstage/plugin-search-backend-node'; -import { createReadStream } from 'fs'; -import { parse } from '@jsonlines/core'; -export class YourCollatorFactory implements DocumentCollatorFactory { - public readonly type: string = 'your-type'; - async getCollator() { - const parseStream = parse(); - return createReadStream('./documents.ndjson').pipe(parseStream); - } -} -``` - -### Rewriting alpha-style decorators for beta - -In alpha versions of the Backstage Search Platform, decorators were classes that -implemented an `execute` method which took an `IndexableDocument` array as an -argument, and resolved a modified array of the same type. - -In beta versions, the logic encapsulated by the aforementioned `execute` method -is contained within an object-mode `Transform` stream which reads objects of -type `IndexableDocument`, and writes objects of a conforming type. Similar to -collators, instances of this stream are instantiated by a factory class -conforming to the `DocumentDecoratorFactory` interface. - -Although you can choose to implement a `Transform` stream from scratch, the -`@backstage/plugin-search-backend-node` package provides a `DecoratorBase` class -in order to simplify the developer experience. With this base class, all that's -needed is to transfer your old decorator class logic into the base class' three -methods (`initialize`, `decorate`, and `finalize`), and implement the factory -class that instantiates the stream: - -```ts -import { DecoratorBase } from '@backstage/plugin-search-backend-node'; -export class YourDecorator extends DecoratorBase { - async initialize() { - // Setup logic. Performed once before any documents are consumed. - } - async decorate( - document: YourIndexableDocument, - ): Promise { - // Perform transformation logic here. - return document; - } - async finalize() { - // Teardown logic. Performed once after all documents have been consumed. - } -} -export class YourDecoratorFactory implements DocumentDecoratorFactory { - async getDecorator() { - return new YourDecorator(); - } -} -``` - -Note the return type of the `decorate` method and how each can be used to -different effect. - -- By resolving a single `YourIndexableDocument` object, your decorator can be - used to make simple transformations: - - ```ts - class BooleanWidgetCoolnessDecorator extends DecoratorBase { - async decorator(widget) { - // Perform a simple, 1:1 transformation. - widget.isCool = widget.isCool === 'true' ? true : false; - return widget; - } - } - ``` - -- By resolving `undefined`, your decorator can filter out documents which - shouldn't be in the index: - - ```ts - class OnlyCoolWidgetsDecorator extends DecoratorBase { - async decorator(widget) { - // Perform a simple filter operation. - return widget.isCool ? widget : undefined; - } - } - ``` - -- By resolving an array of `YourIndexableDocument` objects, you can generate - multiple documents based on the content of one: - - ```ts - class WidgetByVariantDecorator extends DecoratorBase { - async decorator(widget) { - // Generate one widget doc per widget variant. - return widget.variants.map(variant => { - // Each widget doc is the given widget plus a "variant" property - // pulled from a widget.variants string array. - return { - ...widget, - variant, - }; - }); - } - } - ``` - -In alpha versions, a decorator had access to every `IndexableDocument` -simultaneously. This is no longer possible in beta versions (precisely to make -the indexing process more efficient and performant). You will need to modify -your decorator's logic so that it does not need access to every document at -once. - -### Rewriting alpha-style search engines for beta - -Search Engines are responsible for both querying and indexing documents to an -underlying search engine technology. While the search engine query interface -didn't change between alpha and beta versions, the indexing half of the -interface _did_ change. - -In alpha versions of the Backstage Search Platform, a search engine implemented -an `index` method which took a `type` and an `IndexableDocument` array and was -responsible for writing these documents to the underlying search engine. - -In beta versions, the logic encapsulated by the aforementioned `index` method is -contained within an object-mode `Writable` stream which expects objects of type -`IndexableDocument`. On the search engine class itself, the `index` method is -replaced with a `getIndexer` factory method which still takes the `type`, but -resolves an instance of the aforementioned `Writable` stream. - -Although you can choose to implement a `Writable` stream from scratch, the -`@backstage/plugin-search-backend-node` package provides a -`BatchSearchEngineIndexer` class in order to simplify the developer experience. -With this base class, which collects documents in batches of a configurable size -on your behalf, all that's needed is to transfer your old `index` method logic -into the base class' three methods (`initialize`, `index`, and `finalize`), and -implement the factory method that instantiates the stream: - -```ts -import { BatchSearchEngineIndexer } from '@backstage/plugin-search-backend-node'; -import { SearchEngine } from '@backstage/plugin-search-common'; -export class YourSearchEngineIndexer extends BatchSearchEngineIndexer { - constructor({ type }: { type: string }) { - // Customize the number of documents passed to the index method per batch. - super({ batchSize: 500 }); - // An imaginary search engine indexing client. - this.index = new SomeSearchEngineIndex({ indexName: type }); - } - async initialize() { - // Setup logic. Performed once before any documents are consumed. - } - async index(documents: IndexableDocument[]) { - await this.index.batchOf(documents); - } - async finalize() { - // Teardown logic. Performed once after all documents have been consumed. - } -} -export class YourSearchEngine implements SearchEngine { - async getIndexer(type: string) { - return new YourSearchEngineIndexer({ type }); - } -} -``` - ## How to customize search results highlighting styling The default highlighting styling for matched terms in search results is your diff --git a/packages/backend/package.json b/packages/backend/package.json index 67070bfeee..d81af2a3c7 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -51,6 +51,7 @@ "@backstage/plugin-scaffolder-backend": "^1.4.0-next.3", "@backstage/plugin-scaffolder-backend-module-rails": "^0.4.2-next.1", "@backstage/plugin-search-backend": "^0.5.4-next.2", + "@backstage/plugin-search-common": "^0.3.6-next.0", "@backstage/plugin-search-backend-node": "^0.6.3-next.2", "@backstage/plugin-search-backend-module-elasticsearch": "^0.2.0-next.2", "@backstage/plugin-search-backend-module-pg": "^0.3.5-next.2", diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts index c478ecdd05..084b1a54e5 100644 --- a/packages/backend/src/plugins/search.ts +++ b/packages/backend/src/plugins/search.ts @@ -22,8 +22,8 @@ import { PgSearchEngine } from '@backstage/plugin-search-backend-module-pg'; import { IndexBuilder, LunrSearchEngine, - SearchEngine, } from '@backstage/plugin-search-backend-node'; +import { SearchEngine } from '@backstage/plugin-search-common'; import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; import { Router } from 'express'; import { PluginEnvironment } from '../types'; diff --git a/plugins/search-backend-module-pg/api-report.md b/plugins/search-backend-module-pg/api-report.md index f7a188a14e..d1913a535d 100644 --- a/plugins/search-backend-module-pg/api-report.md +++ b/plugins/search-backend-module-pg/api-report.md @@ -9,7 +9,7 @@ import { IndexableDocument } from '@backstage/plugin-search-common'; import { IndexableResultSet } from '@backstage/plugin-search-common'; import { Knex } from 'knex'; import { PluginDatabaseManager } from '@backstage/backend-common'; -import { SearchEngine } from '@backstage/plugin-search-backend-node'; +import { SearchEngine } from '@backstage/plugin-search-common'; import { SearchQuery } from '@backstage/plugin-search-common'; // @public diff --git a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts index abf7cdbf5c..9da5ceec0e 100644 --- a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts +++ b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { PluginDatabaseManager } from '@backstage/backend-common'; -import { SearchEngine } from '@backstage/plugin-search-backend-node'; +import { SearchEngine } from '@backstage/plugin-search-common'; import { SearchQuery, IndexableResultSet, diff --git a/plugins/search-backend-node/api-report.md b/plugins/search-backend-node/api-report.md index ff9480c19d..64957f6366 100644 --- a/plugins/search-backend-node/api-report.md +++ b/plugins/search-backend-node/api-report.md @@ -170,8 +170,6 @@ export type ScheduleTaskParameters = { scheduledRunner: TaskRunner; }; -export { SearchEngine }; - // @public export class TestPipeline { execute(): Promise; diff --git a/plugins/search-backend-node/src/IndexBuilder.test.ts b/plugins/search-backend-node/src/IndexBuilder.test.ts index 70d00f6974..4a2760dde1 100644 --- a/plugins/search-backend-node/src/IndexBuilder.test.ts +++ b/plugins/search-backend-node/src/IndexBuilder.test.ts @@ -19,10 +19,11 @@ import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; import { DocumentCollatorFactory, DocumentDecoratorFactory, + SearchEngine, } from '@backstage/plugin-search-common'; import { Readable, Transform } from 'stream'; import { IndexBuilder } from './IndexBuilder'; -import { LunrSearchEngine, SearchEngine } from './index'; +import { LunrSearchEngine } from './index'; class TestDocumentCollatorFactory implements DocumentCollatorFactory { readonly type: string = 'anything'; diff --git a/plugins/search-backend-node/src/index.ts b/plugins/search-backend-node/src/index.ts index 854881829c..a188509c1c 100644 --- a/plugins/search-backend-node/src/index.ts +++ b/plugins/search-backend-node/src/index.ts @@ -39,8 +39,3 @@ export * from './indexing'; export * from './test-utils'; export type { ScheduleTaskParameters } from './Scheduler'; - -/** - * @deprecated Import from @backstage/plugin-search-common instead - */ -export type { SearchEngine } from '@backstage/plugin-search-common'; diff --git a/plugins/search-backend/api-report.md b/plugins/search-backend/api-report.md index eed61069da..d6274d9a94 100644 --- a/plugins/search-backend/api-report.md +++ b/plugins/search-backend/api-report.md @@ -9,7 +9,7 @@ import express from 'express'; import { Logger } from 'winston'; import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; -import { SearchEngine } from '@backstage/plugin-search-backend-node'; +import { SearchEngine } from '@backstage/plugin-search-common'; // @public (undocumented) export function createRouter(options: RouterOptions): Promise; diff --git a/plugins/search-backend/src/service/router.test.ts b/plugins/search-backend/src/service/router.test.ts index 26d901e519..1437e65b84 100644 --- a/plugins/search-backend/src/service/router.test.ts +++ b/plugins/search-backend/src/service/router.test.ts @@ -17,10 +17,8 @@ import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; -import { - IndexBuilder, - SearchEngine, -} from '@backstage/plugin-search-backend-node'; +import { IndexBuilder } from '@backstage/plugin-search-backend-node'; +import { SearchEngine } from '@backstage/plugin-search-common'; import express from 'express'; import request from 'supertest'; diff --git a/plugins/search-backend/src/service/router.ts b/plugins/search-backend/src/service/router.ts index e4ac233406..cf9abff675 100644 --- a/plugins/search-backend/src/service/router.ts +++ b/plugins/search-backend/src/service/router.ts @@ -33,7 +33,7 @@ import { IndexableResultSet, SearchResultSet, } from '@backstage/plugin-search-common'; -import { SearchEngine } from '@backstage/plugin-search-backend-node'; +import { SearchEngine } from '@backstage/plugin-search-common'; import { AuthorizedSearchEngine } from './AuthorizedSearchEngine'; const jsonObjectSchema: z.ZodSchema = z.lazy(() => { diff --git a/plugins/search/api-report.md b/plugins/search/api-report.md index 800fa7e7fc..e9d8591f92 100644 --- a/plugins/search/api-report.md +++ b/plugins/search/api-report.md @@ -6,143 +6,24 @@ /// import { BackstagePlugin } from '@backstage/core-plugin-api'; -import { DefaultResultListItemProps } from '@backstage/plugin-search-react'; import { IconComponent } from '@backstage/core-plugin-api'; -import { InputBaseProps } from '@material-ui/core'; -import { ReactElement } from 'react'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; -import { SearchAutocompleteFilterProps as SearchAutocompleteFilterProps_2 } from '@backstage/plugin-search-react'; -import { SearchBarBaseProps as SearchBarBaseProps_2 } from '@backstage/plugin-search-react'; -import { SearchFilterComponentProps as SearchFilterComponentProps_2 } from '@backstage/plugin-search-react'; -import { SearchResultProps } from '@backstage/plugin-search-react'; - -// @public @deprecated (undocumented) -export const DefaultResultListItem: ( - props: DefaultResultListItemProps, -) => JSX.Element; - -// @public @deprecated (undocumented) -export type FilterOptions = { - kind: Array; - lifecycle: Array; -}; - -// @public @deprecated (undocumented) -export const Filters: ({ - filters, - filterOptions, - resetFilters, - updateSelected, - updateChecked, -}: FiltersProps) => JSX.Element; - -// @public @deprecated (undocumented) -export const FiltersButton: ({ - numberOfSelectedFilters, - handleToggleFilters, -}: FiltersButtonProps) => JSX.Element; - -// @public @deprecated -export type FiltersButtonProps = { - numberOfSelectedFilters: number; - handleToggleFilters: () => void; -}; - -// @public @deprecated -export type FiltersProps = { - filters: FiltersState; - filterOptions: FilterOptions; - resetFilters: () => void; - updateSelected: (filter: string) => void; - updateChecked: (filter: string) => void; -}; - -// @public @deprecated (undocumented) -export type FiltersState = { - selected: string; - checked: Array; -}; +import { SearchBarBaseProps } from '@backstage/plugin-search-react'; // @public (undocumented) export const HomePageSearchBar: ({ ...props -}: Partial>) => JSX.Element; +}: Partial>) => JSX.Element; // @public export type HomePageSearchBarProps = Partial< - Omit + Omit >; // @public (undocumented) export const Router: () => JSX.Element; -// @public @deprecated (undocumented) -export type SearchAutocompleteFilterProps = SearchFilterComponentProps_2 & { - filterSelectedOptions?: boolean; - limitTags?: number; - multiple?: boolean; -}; - -// @public @deprecated -export const SearchBar: ({ onChange, ...props }: SearchBarProps) => JSX.Element; - -// @public @deprecated -export const SearchBarBase: ({ - onChange, - onKeyDown, - onSubmit, - debounceTime, - clearButton, - fullWidth, - value: defaultValue, - inputProps: defaultInputProps, - endAdornment: defaultEndAdornment, - ...props -}: SearchBarBaseProps_2) => JSX.Element; - -// @public @deprecated -export type SearchBarBaseProps = Omit & { - debounceTime?: number; - clearButton?: boolean; - onClear?: () => void; - onSubmit?: () => void; - onChange: (value: string) => void; -}; - -// @public @deprecated -export type SearchBarProps = Partial; - -// @public @deprecated (undocumented) -export const SearchFilter: { - ({ component: Element, ...props }: SearchFilterWrapperProps): JSX.Element; - Checkbox( - props: Omit & - SearchFilterComponentProps, - ): JSX.Element; - Select( - props: Omit & - SearchFilterComponentProps, - ): JSX.Element; - Autocomplete(props: SearchAutocompleteFilterProps_2): JSX.Element; -}; - -// @public @deprecated (undocumented) -export type SearchFilterComponentProps = { - className?: string; - name: string; - label?: string; - values?: string[] | ((partial: string) => Promise); - defaultValue?: string[] | string | null; - valuesDebounceMs?: number; -}; - -// @public @deprecated (undocumented) -export type SearchFilterWrapperProps = SearchFilterComponentProps & { - component: (props: SearchFilterComponentProps) => ReactElement; - debug?: boolean; -}; - // @public (undocumented) export const SearchModal: ({ open, @@ -199,12 +80,6 @@ const searchPlugin: BackstagePlugin< export { searchPlugin as plugin }; export { searchPlugin }; -// @public @deprecated (undocumented) -export const SearchResult: (props: SearchResultProps) => JSX.Element; - -// @public @deprecated (undocumented) -export const SearchResultPager: () => JSX.Element; - // @public (undocumented) export const SearchType: { (props: SearchTypeProps): JSX.Element; diff --git a/plugins/search/src/components/Filters/Filters.tsx b/plugins/search/src/components/Filters/Filters.tsx deleted file mode 100644 index 9170961e18..0000000000 --- a/plugins/search/src/components/Filters/Filters.tsx +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { - makeStyles, - Typography, - Divider, - Card, - CardHeader, - Button, - CardContent, - Select, - Checkbox, - List, - ListItem, - ListItemText, - MenuItem, -} from '@material-ui/core'; - -const useStyles = makeStyles(theme => ({ - filters: { - background: 'transparent', - boxShadow: '0px 0px 0px 0px', - }, - checkbox: { - padding: theme.spacing(0, 1, 0, 1), - }, - dropdown: { - width: '100%', - }, -})); - -/** - * @public - * @deprecated This type and corresponding component will be removed in a - * future release. - */ -export type FiltersState = { - selected: string; - checked: Array; -}; - -/** - * @public - * @deprecated This type and corresponding component will be removed in a - * future release. - */ -export type FilterOptions = { - kind: Array; - lifecycle: Array; -}; - -/** - * Props for {@link Filters}. - * - * @public - * @deprecated This type and corresponding component will be removed in a - * future release. - */ -export type FiltersProps = { - filters: FiltersState; - filterOptions: FilterOptions; - resetFilters: () => void; - updateSelected: (filter: string) => void; - updateChecked: (filter: string) => void; -}; - -/** - * @public - * @deprecated This component will be removed in a future release. Use - * `SearchFilter` from `@backstage/plugin-search-react` instead. - */ -export const Filters = ({ - filters, - filterOptions, - resetFilters, - updateSelected, - updateChecked, -}: FiltersProps) => { - const classes = useStyles(); - - return ( - - Filters} - action={ - - } - /> - - {filterOptions.kind.length === 0 && filterOptions.lifecycle.length === 0 && ( - - - Filters cannot be applied to available results - - - )} - {filterOptions.kind.length > 0 && ( - - Kind - - - )} - {filterOptions.lifecycle.length > 0 && ( - - Lifecycle - - {filterOptions.lifecycle.map(filter => ( - updateChecked(filter)} - > - - - - ))} - - - )} - - ); -}; diff --git a/plugins/search/src/components/Filters/FiltersButton.tsx b/plugins/search/src/components/Filters/FiltersButton.tsx deleted file mode 100644 index 890c787689..0000000000 --- a/plugins/search/src/components/Filters/FiltersButton.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import FilterListIcon from '@material-ui/icons/FilterList'; -import { makeStyles, IconButton, Typography } from '@material-ui/core'; - -const useStyles = makeStyles(theme => ({ - filters: { - width: '250px', - display: 'flex', - }, - icon: { - margin: theme.spacing(-1, 0, 0, 0), - }, -})); - -/** - * Props for {@link FiltersButton}. - * - * @public - * @deprecated This type and corresponding component will be removed in a - * future release. - */ -export type FiltersButtonProps = { - numberOfSelectedFilters: number; - handleToggleFilters: () => void; -}; - -/** - * @public - * @deprecated See `SearchFilter` in `@backstage/plugin-search-react` instead. - */ -export const FiltersButton = ({ - numberOfSelectedFilters, - handleToggleFilters, -}: FiltersButtonProps) => { - const classes = useStyles(); - - return ( -
- - - - - Filters ({numberOfSelectedFilters ? numberOfSelectedFilters : 0}) - -
- ); -}; diff --git a/plugins/search/src/components/Filters/index.tsx b/plugins/search/src/components/Filters/index.tsx deleted file mode 100644 index dc4d811dbd..0000000000 --- a/plugins/search/src/components/Filters/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { FiltersButton } from './FiltersButton'; -export type { FiltersButtonProps } from './FiltersButton'; -export { Filters } from './Filters'; -export type { FilterOptions, FiltersProps, FiltersState } from './Filters'; diff --git a/plugins/search/src/components/LegacySearchPage/Filters/Filters.tsx b/plugins/search/src/components/LegacySearchPage/Filters/Filters.tsx deleted file mode 100644 index a0ca4914a2..0000000000 --- a/plugins/search/src/components/LegacySearchPage/Filters/Filters.tsx +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { - makeStyles, - Typography, - Divider, - Card, - CardHeader, - Button, - CardContent, - Select, - Checkbox, - List, - ListItem, - ListItemText, - MenuItem, -} from '@material-ui/core'; - -const useStyles = makeStyles(theme => ({ - filters: { - background: 'transparent', - boxShadow: '0px 0px 0px 0px', - }, - checkbox: { - padding: theme.spacing(0, 1, 0, 1), - }, - dropdown: { - width: '100%', - }, -})); - -export type FiltersState = { - selected: string; - checked: Array; -}; - -export type FilterOptions = { - kind: Array; - lifecycle: Array; -}; - -type FiltersProps = { - filters: FiltersState; - filterOptions: FilterOptions; - resetFilters: () => void; - updateSelected: (filter: string) => void; - updateChecked: (filter: string) => void; -}; - -export const Filters = ({ - filters, - filterOptions, - resetFilters, - updateSelected, - updateChecked, -}: FiltersProps) => { - const classes = useStyles(); - - return ( - - Filters} - action={ - - } - /> - - {filterOptions.kind.length === 0 && filterOptions.lifecycle.length === 0 && ( - - - Filters cannot be applied to available results - - - )} - {filterOptions.kind.length > 0 && ( - - Kind - - - )} - {filterOptions.lifecycle.length > 0 && ( - - Lifecycle - - {filterOptions.lifecycle.map(filter => ( - updateChecked(filter)} - > - - - - ))} - - - )} - - ); -}; diff --git a/plugins/search/src/components/LegacySearchPage/Filters/FiltersButton.tsx b/plugins/search/src/components/LegacySearchPage/Filters/FiltersButton.tsx deleted file mode 100644 index 1c2829defe..0000000000 --- a/plugins/search/src/components/LegacySearchPage/Filters/FiltersButton.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import FilterListIcon from '@material-ui/icons/FilterList'; -import { makeStyles, IconButton, Typography } from '@material-ui/core'; - -const useStyles = makeStyles(theme => ({ - filters: { - width: '250px', - display: 'flex', - }, - icon: { - margin: theme.spacing(-1, 0, 0, 0), - }, -})); - -type FiltersButtonProps = { - numberOfSelectedFilters: number; - handleToggleFilters: () => void; -}; - -export const FiltersButton = ({ - numberOfSelectedFilters, - handleToggleFilters, -}: FiltersButtonProps) => { - const classes = useStyles(); - - return ( -
- - - - - Filters ({numberOfSelectedFilters ? numberOfSelectedFilters : 0}) - -
- ); -}; diff --git a/plugins/search/src/components/LegacySearchPage/Filters/index.ts b/plugins/search/src/components/LegacySearchPage/Filters/index.ts deleted file mode 100644 index de890e0eaf..0000000000 --- a/plugins/search/src/components/LegacySearchPage/Filters/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { FiltersButton } from './FiltersButton'; -export { Filters } from './Filters'; -export type { FiltersState } from './Filters'; diff --git a/plugins/search/src/components/LegacySearchPage/LegacySearchBar.tsx b/plugins/search/src/components/LegacySearchPage/LegacySearchBar.tsx deleted file mode 100644 index 44fb006af3..0000000000 --- a/plugins/search/src/components/LegacySearchPage/LegacySearchBar.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import { Paper } from '@material-ui/core'; -import InputBase from '@material-ui/core/InputBase'; -import IconButton from '@material-ui/core/IconButton'; -import SearchIcon from '@material-ui/icons/Search'; -import ClearButton from '@material-ui/icons/Clear'; - -const useStyles = makeStyles(() => ({ - root: { - display: 'flex', - alignItems: 'center', - }, - input: { - flex: 1, - }, -})); - -type SearchBarProps = { - searchQuery: string; - handleSearch: any; - handleClearSearchBar: any; -}; - -export const SearchBar = ({ - searchQuery, - handleSearch, - handleClearSearchBar, -}: SearchBarProps) => { - const classes = useStyles(); - - return ( - handleSearch(e)} - className={classes.root} - > - - - - handleSearch(e)} - inputProps={{ 'aria-label': 'search backstage' }} - /> - handleClearSearchBar()}> - - - - ); -}; diff --git a/plugins/search/src/components/LegacySearchPage/LegacySearchPage.tsx b/plugins/search/src/components/LegacySearchPage/LegacySearchPage.tsx deleted file mode 100644 index 09a33b9b74..0000000000 --- a/plugins/search/src/components/LegacySearchPage/LegacySearchPage.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Grid } from '@material-ui/core'; -import React, { useEffect, useState } from 'react'; -import useDebounce from 'react-use/lib/useDebounce'; -import { SearchBar } from './LegacySearchBar'; -import { SearchResult } from './LegacySearchResult'; -import { - Content, - Header, - Page, - useQueryParamState, -} from '@backstage/core-components'; - -/** - * @deprecated This SearchPage, powered directly by the Catalog API, will be - * removed from a future release of this plugin. - */ -export const LegacySearchPage = () => { - const [queryString, setQueryString] = useQueryParamState('query'); - const [searchQuery, setSearchQuery] = useState(queryString ?? ''); - - const handleSearch = (event: React.ChangeEvent) => { - event.preventDefault(); - setSearchQuery(event.target.value); - }; - - useEffect(() => setSearchQuery(queryString ?? ''), [queryString]); - - useDebounce( - () => { - setQueryString(searchQuery); - }, - 200, - [searchQuery], - ); - - const handleClearSearchBar = () => { - setSearchQuery(''); - }; - - return ( - -
- - - - - - - - - - - - ); -}; diff --git a/plugins/search/src/components/LegacySearchPage/LegacySearchResult.tsx b/plugins/search/src/components/LegacySearchPage/LegacySearchResult.tsx deleted file mode 100644 index ba92dba710..0000000000 --- a/plugins/search/src/components/LegacySearchPage/LegacySearchResult.tsx +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { Divider, Grid, makeStyles, Typography } from '@material-ui/core'; -import { Alert } from '@material-ui/lab'; -import React, { useEffect, useState } from 'react'; -import useAsync from 'react-use/lib/useAsync'; -import { catalogApiRef } from '@backstage/plugin-catalog-react'; - -import { Filters, FiltersButton, FiltersState } from './Filters'; -import { Entity, DEFAULT_NAMESPACE } from '@backstage/catalog-model'; - -import { - EmptyState, - Link, - Progress, - Table, - TableColumn, -} from '@backstage/core-components'; -import { useApi } from '@backstage/core-plugin-api'; - -type Result = { - name: string; - description: string | undefined; - owner: string | undefined; - kind: string; - lifecycle: string | undefined; - url: string; -}; -type SearchResults = Array; - -const useStyles = makeStyles(theme => ({ - searchQuery: { - color: theme.palette.text.primary, - background: theme.palette.background.default, - borderRadius: '10%', - }, - tableHeader: { - margin: theme.spacing(1, 0, 0, 0), - display: 'flex', - }, - divider: { - width: '1px', - margin: theme.spacing(0, 2), - padding: theme.spacing(2, 0), - }, -})); - -type SearchResultProps = { - searchQuery?: string; -}; - -type TableHeaderProps = { - searchQuery?: string; - numberOfSelectedFilters: number; - numberOfResults: number; - handleToggleFilters: () => void; -}; - -// TODO: move out column to make the search result component more generic -const columns: TableColumn[] = [ - { - title: 'Name', - field: 'name', - highlight: true, - render: (result: Partial) => ( - {result.name} - ), - }, - { - title: 'Description', - field: 'description', - }, - { - title: 'Owner', - field: 'owner', - }, - { - title: 'Kind', - field: 'kind', - }, - { - title: 'LifeCycle', - field: 'lifecycle', - }, -]; - -const TableHeader = ({ - searchQuery, - numberOfSelectedFilters, - numberOfResults, - handleToggleFilters, -}: TableHeaderProps) => { - const classes = useStyles(); - - return ( -
- - - - {searchQuery ? ( - - {`${numberOfResults} `} - {numberOfResults > 1 ? `results for ` : `result for `} - "{searchQuery}"{' '} - - ) : ( - {`${numberOfResults} results`} - )} - -
- ); -}; - -export const SearchResult = ({ searchQuery }: SearchResultProps) => { - const catalogApi = useApi(catalogApiRef); - - const [showFilters, toggleFilters] = useState(false); - const [selectedFilters, setSelectedFilters] = useState({ - selected: '', - checked: [], - }); - - const [filteredResults, setFilteredResults] = useState([]); - - const { - loading, - error, - value: results, - } = useAsync(async () => { - const entities = await catalogApi.getEntities(); - return entities.items.map((entity: Entity) => ({ - name: entity.metadata.name, - description: entity.metadata.description, - owner: - typeof entity.spec?.owner === 'string' ? entity.spec?.owner : undefined, - kind: entity.kind, - lifecycle: - typeof entity.spec?.lifecycle === 'string' - ? entity.spec?.lifecycle - : undefined, - url: `/catalog/${ - entity.metadata.namespace?.toLocaleLowerCase('en-US') || - DEFAULT_NAMESPACE - }/${entity.kind.toLocaleLowerCase('en-US')}/${entity.metadata.name}`, - })); - }, []); - - useEffect(() => { - if (results) { - let withFilters = results; - - // apply filters - - // filter on selected - if (selectedFilters.selected !== '') { - withFilters = results.filter((result: Result) => - selectedFilters.selected.includes(result.kind), - ); - } - - // filter on checked - if (selectedFilters.checked.length > 0) { - withFilters = withFilters.filter( - (result: Result) => - result.lifecycle && - selectedFilters.checked.includes(result.lifecycle), - ); - } - - // filter on searchQuery - if (searchQuery) { - withFilters = withFilters.filter( - (result: Result) => - result.name?.toLocaleLowerCase('en-US').includes(searchQuery) || - result.name - ?.toLocaleLowerCase('en-US') - .includes(searchQuery.split(' ').join('-')) || - result.description - ?.toLocaleLowerCase('en-US') - .includes(searchQuery), - ); - } - - setFilteredResults(withFilters); - } - }, [selectedFilters, searchQuery, results]); - if (loading) { - return ; - } - if (error) { - return ( - - Error encountered while fetching search results. {error.toString()} - - ); - } - if (!results || results.length === 0) { - return ; - } - - const resetFilters = () => { - setSelectedFilters({ - selected: '', - checked: [], - }); - }; - - const updateSelected = (filter: string) => { - setSelectedFilters(prevState => ({ - ...prevState, - selected: filter, - })); - }; - - const updateChecked = (filter: string) => { - if (selectedFilters.checked.includes(filter)) { - setSelectedFilters(prevState => ({ - ...prevState, - checked: prevState.checked.filter(item => item !== filter), - })); - return; - } - - setSelectedFilters(prevState => ({ - ...prevState, - checked: [...prevState.checked, filter], - })); - }; - - const filterOptions = results.reduce( - (acc, curr) => { - if (curr.kind && acc.kind.indexOf(curr.kind) < 0) { - acc.kind.push(curr.kind); - } - if (curr.lifecycle && acc.lifecycle.indexOf(curr.lifecycle) < 0) { - acc.lifecycle.push(curr.lifecycle); - } - return acc; - }, - { - kind: [] as Array, - lifecycle: [] as Array, - }, - ); - - return ( - <> - - {showFilters && ( - - - - )} - - toggleFilters(!showFilters)} - /> - } - /> - - - - ); -}; diff --git a/plugins/search/src/components/LegacySearchPage/index.ts b/plugins/search/src/components/LegacySearchPage/index.ts deleted file mode 100644 index 5b64054f8d..0000000000 --- a/plugins/search/src/components/LegacySearchPage/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { LegacySearchPage } from './LegacySearchPage'; diff --git a/plugins/search/src/components/SearchBar/SearchBar.tsx b/plugins/search/src/components/SearchBar/SearchBar.tsx deleted file mode 100644 index a854869976..0000000000 --- a/plugins/search/src/components/SearchBar/SearchBar.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { useCallback } from 'react'; - -import { InputBaseProps } from '@material-ui/core'; - -import { - SearchBarBase as RealSearchBarBase, - useSearch, -} from '@backstage/plugin-search-react'; - -/** - * Props for {@link SearchBarBase}. - * - * @public - * @deprecated Import from `@backstage/plugin-search-react` instead. - */ -export type SearchBarBaseProps = Omit & { - debounceTime?: number; - clearButton?: boolean; - onClear?: () => void; - onSubmit?: () => void; - onChange: (value: string) => void; -}; - -/** - * All search boxes exported by the search plugin are based on the , - * and this one is based on the component from Material UI. - * Recommended if you don't use Search Provider or Search Context. - * - * @public - * @deprecated Import from `@backstage/plugin-search-react` instead. - */ -export const SearchBarBase = RealSearchBarBase; - -/** - * Props for {@link SearchBar}. - * - * @public - * @deprecated Import from `@backstage/plugin-search-react` instead. - */ -export type SearchBarProps = Partial; - -/** - * Recommended search bar when you use the Search Provider or Search Context. - * - * @public - * @deprecated Import from `@backstage/plugin-search-react` instead. - */ -export const SearchBar = ({ onChange, ...props }: SearchBarProps) => { - const { term, setTerm } = useSearch(); - - const handleChange = useCallback( - (newValue: string) => { - if (onChange) { - onChange(newValue); - } else { - setTerm(newValue); - } - }, - [onChange, setTerm], - ); - - return ; -}; diff --git a/plugins/search/src/components/SearchBar/index.tsx b/plugins/search/src/components/SearchBar/index.tsx deleted file mode 100644 index f95d2ec0fc..0000000000 --- a/plugins/search/src/components/SearchBar/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { SearchBar, SearchBarBase } from './SearchBar'; -export type { SearchBarProps, SearchBarBaseProps } from './SearchBar'; diff --git a/plugins/search/src/components/SearchFilter/SearchFilter.Autocomplete.test.tsx b/plugins/search/src/components/SearchFilter/SearchFilter.Autocomplete.test.tsx deleted file mode 100644 index 15ad348bd4..0000000000 --- a/plugins/search/src/components/SearchFilter/SearchFilter.Autocomplete.test.tsx +++ /dev/null @@ -1,363 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { TestApiProvider } from '@backstage/test-utils'; -import { screen, render, waitFor, within } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; -import { - SearchContextProvider, - useSearch, - searchApiRef, -} from '@backstage/plugin-search-react'; -import { SearchFilter } from './SearchFilter'; - -const SearchContextFilterSpy = ({ name }: { name: string }) => { - const { filters } = useSearch(); - const value = filters[name]; - return ( - - {Array.isArray(value) ? value.join(',') : value} - - ); -}; - -describe('SearchFilter.Autocomplete', () => { - const query = jest.fn().mockResolvedValue({}); - const emptySearchContext = { - term: '', - types: [], - filters: {}, - }; - - const name = 'field'; - const values = ['value1', 'value2']; - - it('renders as expected', async () => { - render( - - - - - , - ); - - const autocomplete = screen.getByRole('combobox'); - const input = within(autocomplete).getByRole('textbox'); - await userEvent.click(input); - - await waitFor(() => { - screen.getByRole('listbox'); - }); - - expect(screen.getByRole('option', { name: values[0] })).toBeInTheDocument(); - expect(screen.getByRole('option', { name: values[1] })).toBeInTheDocument(); - }); - - it('renders as expected with async values', async () => { - render( - - - values} /> - - , - ); - - const autocomplete = screen.getByRole('combobox'); - const input = within(autocomplete).getByRole('textbox'); - await userEvent.click(input); - - await waitFor(() => { - screen.getByRole('listbox'); - }); - - expect(screen.getByRole('option', { name: values[0] })).toBeInTheDocument(); - expect(screen.getByRole('option', { name: values[1] })).toBeInTheDocument(); - }); - - it('does not affect unrelated filter state', async () => { - render( - - - - - - - , - ); - - // The spy should show the initial value. - expect(screen.getByTestId('unrelated-filter-spy')).toHaveTextContent( - 'value', - ); - - // Select a value from the autocomplete filter. - const autocomplete = screen.getByRole('combobox'); - const input = within(autocomplete).getByRole('textbox'); - await userEvent.click(input); - await waitFor(() => { - screen.getByRole('listbox'); - }); - await userEvent.click(screen.getByRole('option', { name: values[1] })); - - // Wait for the autocomplete filter's value to change. - await waitFor(() => { - expect(screen.getByTestId(`${name}-filter-spy`)).toHaveTextContent( - values[1], - ); - }); - - // Unrelated filter spy should maintain the same value. - expect(screen.getByTestId('unrelated-filter-spy')).toHaveTextContent( - 'value', - ); - }); - - describe('single', () => { - it('renders as expected with defaultValue', async () => { - render( - - - - - - , - ); - - const autocomplete = screen.getByRole('combobox'); - const input = within(autocomplete).getByRole('textbox'); - - await waitFor(() => { - expect(input).toHaveValue(values[1]); - expect(screen.getByTestId(`${name}-filter-spy`)).toHaveTextContent( - values[1], - ); - }); - }); - - it('renders as expected with initial context', async () => { - render( - - - - - - , - ); - - const autocomplete = screen.getByRole('combobox'); - const input = within(autocomplete).getByRole('textbox'); - - await waitFor(() => { - expect(input).toHaveValue(values[0]); - expect(screen.getByTestId(`${name}-filter-spy`)).toHaveTextContent( - values[0], - ); - }); - }); - - it('sets filter state when selecting a value', async () => { - render( - - - - - - , - ); - - // Select the first option in the autocomplete. - const autocomplete = screen.getByRole('combobox'); - const input = within(autocomplete).getByRole('textbox'); - await userEvent.click(input); - await waitFor(() => { - screen.getByRole('listbox'); - }); - await userEvent.click(screen.getByRole('option', { name: values[0] })); - - // The value should be present in the context. - await waitFor(() => { - expect(screen.getByTestId(`${name}-filter-spy`)).toHaveTextContent( - values[0], - ); - }); - - // Click the "Clear" button to remove the value. - const clearButton = within(autocomplete).getByLabelText('Clear'); - await userEvent.click(clearButton); - - // That value should have been unset from the context. - await waitFor(() => { - expect(screen.getByTestId(`${name}-filter-spy`)).toHaveTextContent(''); - }); - }); - }); - - describe('multiple', () => { - it('renders as expected with defaultValue', async () => { - render( - - - - - - , - ); - - await waitFor(() => { - expect(screen.getByText(values[0])).toBeInTheDocument(); - expect(screen.getByText(values[1])).toBeInTheDocument(); - expect(screen.getByTestId(`${name}-filter-spy`)).toHaveTextContent( - values.join(','), - ); - }); - }); - - it('renders as expected with initial context', async () => { - render( - - - - - - , - ); - - await waitFor(() => { - expect(screen.getByText(values[0])).toBeInTheDocument(); - expect(screen.getByText(values[1])).toBeInTheDocument(); - expect(screen.getByTestId(`${name}-filter-spy`)).toHaveTextContent( - values.join(','), - ); - }); - }); - - it('respects tag limit configuration', async () => { - render( - - - - - , - ); - - const autocomplete = screen.getByRole('combobox'); - const input = within(autocomplete).getByRole('textbox'); - - // Select the second value. - await userEvent.click(input); - await waitFor(() => { - screen.getByRole('listbox'); - }); - await userEvent.click(screen.getByRole('option', { name: values[1] })); - await waitFor(() => { - expect( - screen.getByRole('button', { name: values[1] }), - ).toBeInTheDocument(); - }); - - // Select the first value. - await userEvent.click(input); - await waitFor(() => { - screen.getByRole('listbox'); - }); - await userEvent.click(screen.getByRole('option', { name: values[0] })); - await waitFor(() => { - expect( - screen.getByRole('button', { name: values[0] }), - ).toBeInTheDocument(); - }); - - // Blur the field and only one tag should be shown with a +1. - input.blur(); - expect( - screen.queryByRole('button', { name: values[0] }), - ).not.toBeInTheDocument(); - expect(screen.getByText('+1')).toBeInTheDocument(); - }); - - it('sets filter state when selecting a value', async () => { - render( - - - - - - , - ); - - const autocomplete = screen.getByRole('combobox'); - - // Select both values in the autocomplete. - const input = within(autocomplete).getByRole('textbox'); - await userEvent.click(input); - await waitFor(() => { - screen.getByRole('listbox'); - }); - await userEvent.click(screen.getByRole('option', { name: values[0] })); - await userEvent.click(input); - await waitFor(() => { - screen.getByRole('listbox'); - }); - await userEvent.click(screen.getByRole('option', { name: values[1] })); - - // Both options should be present in the context. - await waitFor(() => { - expect(screen.getByTestId(`${name}-filter-spy`)).toHaveTextContent( - values.join(','), - ); - }); - - // Click the "Clear" button to remove the value. - const clearButton = within(autocomplete).getByLabelText('Clear'); - await userEvent.click(clearButton); - - // There should be no content in the filter context. - await waitFor(() => { - expect(screen.getByTestId(`${name}-filter-spy`)).toHaveTextContent(''); - }); - }); - }); -}); diff --git a/plugins/search/src/components/SearchFilter/SearchFilter.Autocomplete.tsx b/plugins/search/src/components/SearchFilter/SearchFilter.Autocomplete.tsx deleted file mode 100644 index 3db5112b88..0000000000 --- a/plugins/search/src/components/SearchFilter/SearchFilter.Autocomplete.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { SearchFilterComponentProps } from '@backstage/plugin-search-react'; - -/** - * @public - * @deprecated Import from `@backstage/plugin-search-react` instead. - */ -export type SearchAutocompleteFilterProps = SearchFilterComponentProps & { - filterSelectedOptions?: boolean; - limitTags?: number; - multiple?: boolean; -}; diff --git a/plugins/search/src/components/SearchFilter/SearchFilter.test.tsx b/plugins/search/src/components/SearchFilter/SearchFilter.test.tsx deleted file mode 100644 index 2d652df483..0000000000 --- a/plugins/search/src/components/SearchFilter/SearchFilter.test.tsx +++ /dev/null @@ -1,409 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { screen, render, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { SearchFilter } from './SearchFilter'; - -import { SearchContextProvider } from '@backstage/plugin-search-react'; -import { useApi } from '@backstage/core-plugin-api'; - -jest.mock('@backstage/core-plugin-api', () => ({ - ...jest.requireActual('@backstage/core-plugin-api'), - useApi: jest.fn().mockReturnValue({}), -})); - -describe('SearchFilter', () => { - const initialState = { - term: '', - filters: {}, - types: [], - }; - - const label = 'Field'; - const name = 'field'; - const values = ['value1', 'value2']; - const filters = { unrelated: 'unrelated' }; - - const query = jest.fn().mockResolvedValue({}); - (useApi as jest.Mock).mockReturnValue({ query: query }); - - afterAll(() => { - jest.resetAllMocks(); - }); - - it('Check that element was rendered and received props', async () => { - const CustomFilter = (props: { name: string }) =>
{props.name}
; - - render(); - - expect(screen.getByRole('heading', { name })).toBeInTheDocument(); - }); - - describe('Checkbox', () => { - it('Renders field name and values when provided as props', async () => { - render( - - - , - ); - - await waitFor(() => { - expect(screen.getByText(label)).toBeInTheDocument(); - }); - - expect( - screen.getByRole('checkbox', { name: values[0] }), - ).toBeInTheDocument(); - expect( - screen.getByRole('checkbox', { name: values[1] }), - ).toBeInTheDocument(); - }); - - it('Renders correctly based on filter state', async () => { - render( - - - , - ); - - await waitFor(() => { - expect(screen.getByText(label)).toBeInTheDocument(); - }); - - expect( - screen.getByRole('checkbox', { name: values[0] }), - ).not.toBeChecked(); - expect(screen.getByRole('checkbox', { name: values[1] })).toBeChecked(); - }); - - it('Renders correctly based on defaultValue', async () => { - render( - - - , - ); - - await waitFor(() => { - expect(screen.getByText(label)).toBeInTheDocument(); - }); - - expect(screen.getByRole('checkbox', { name: values[0] })).toBeChecked(); - expect( - screen.getByRole('checkbox', { name: values[1] }), - ).not.toBeChecked(); - }); - - it('Checking / unchecking a value sets filter state', async () => { - render( - - - , - ); - - await waitFor(() => { - expect(screen.getByText(label)).toBeInTheDocument(); - }); - - const checkBox = screen.getByRole('checkbox', { name: values[0] }); - - // Check the box. - await userEvent.click(checkBox); - await waitFor(() => { - expect(query).toHaveBeenLastCalledWith( - expect.objectContaining({ filters: { field: [values[0]] } }), - ); - }); - - // Uncheck the box. - await userEvent.click(checkBox); - await waitFor(() => { - expect(query).toHaveBeenLastCalledWith( - expect.objectContaining({ filters: {} }), - ); - }); - }); - - it('Checking / unchecking a value maintains unrelated filter state', async () => { - render( - - - , - ); - - await waitFor(() => { - expect(screen.getByText(label)).toBeInTheDocument(); - }); - - const checkBox = screen.getByRole('checkbox', { name: values[0] }); - - // Check the box. - await userEvent.click(checkBox); - await waitFor(() => { - expect(query).toHaveBeenLastCalledWith( - expect.objectContaining({ - filters: { ...filters, field: [values[0]] }, - }), - ); - }); - - // Uncheck the box. - await userEvent.click(checkBox); - await waitFor(() => { - expect(query).toHaveBeenLastCalledWith( - expect.objectContaining({ filters }), - ); - }); - }); - }); - - describe('Select', () => { - it('Renders field name and values when provided as props', async () => { - render( - - - , - ); - - await waitFor(() => { - expect(screen.getByText(label)).toBeInTheDocument(); - }); - - await userEvent.click(screen.getByRole('button')); - - await waitFor(() => { - expect(screen.getByRole('listbox')).toBeInTheDocument(); - }); - - expect( - screen.getByRole('option', { name: values[0] }), - ).toBeInTheDocument(); - expect( - screen.getByRole('option', { name: values[1] }), - ).toBeInTheDocument(); - }); - - it('Renders values when provided asynchronously', async () => { - render( - - values} - /> - , - ); - - await waitFor(() => { - expect(screen.getByRole('button')).toBeInTheDocument(); - expect( - screen.getByRole('button').getAttribute('aria-disabled'), - ).not.toBe('true'); - }); - - await userEvent.click(screen.getByRole('button')); - - await waitFor(() => { - expect(screen.getByRole('listbox')).toBeInTheDocument(); - }); - - expect( - screen.getByRole('option', { name: values[0] }), - ).toBeInTheDocument(); - expect( - screen.getByRole('option', { name: values[1] }), - ).toBeInTheDocument(); - }); - - it('Renders correctly based on filter state', async () => { - render( - - - , - ); - - await waitFor(() => { - expect(screen.getByText(label)).toBeInTheDocument(); - }); - - await userEvent.click(screen.getByRole('button')); - - await waitFor(() => { - expect(screen.getByRole('listbox')).toBeInTheDocument(); - }); - - expect(screen.getByRole('option', { name: values[0] })).toHaveAttribute( - 'aria-selected', - 'true', - ); - expect( - screen.getByRole('option', { name: values[1] }), - ).not.toHaveAttribute('aria-selected'); - expect(screen.getByRole('option', { name: 'All' })).not.toHaveAttribute( - 'aria-selected', - ); - }); - - it('Renders correctly based on defaultValue', async () => { - render( - - - , - ); - - await waitFor(() => { - expect(screen.getByText(label)).toBeInTheDocument(); - }); - - await userEvent.click(screen.getByRole('button')); - - await waitFor(() => { - expect(screen.getByRole('listbox')).toBeInTheDocument(); - }); - - expect(screen.getByRole('option', { name: values[0] })).toHaveAttribute( - 'aria-selected', - 'true', - ); - expect( - screen.getByRole('option', { name: values[1] }), - ).not.toHaveAttribute('aria-selected'); - expect(screen.getByRole('option', { name: 'All' })).not.toHaveAttribute( - 'aria-selected', - ); - }); - - it('Selecting a value sets filter state', async () => { - render( - - - , - ); - - await waitFor(() => { - expect(screen.getByText(label)).toBeInTheDocument(); - }); - - const button = screen.getByRole('button'); - - await userEvent.click(button); - - await waitFor(() => { - expect(screen.getByRole('listbox')).toBeInTheDocument(); - }); - - await userEvent.click(screen.getByRole('option', { name: values[0] })); - - await waitFor(() => { - expect(query).toHaveBeenLastCalledWith( - expect.objectContaining({ - filters: { [name]: values[0] }, - }), - ); - }); - - await userEvent.click(button); - - await waitFor(() => { - expect(screen.getByRole('listbox')).toBeInTheDocument(); - }); - - await userEvent.click(screen.getByRole('option', { name: 'All' })); - - await waitFor(() => { - expect(query).toHaveBeenLastCalledWith( - expect.objectContaining({ - filters: {}, - }), - ); - }); - }); - - it('Selecting a value maintains unrelated filter state', async () => { - render( - - - , - ); - - await waitFor(() => { - expect(screen.getByText(label)).toBeInTheDocument(); - }); - - const button = screen.getByRole('button'); - - await userEvent.click(button); - - await waitFor(() => { - expect(screen.getByRole('listbox')).toBeInTheDocument(); - }); - - await userEvent.click(screen.getByRole('option', { name: values[0] })); - - await waitFor(() => { - expect(query).toHaveBeenLastCalledWith( - expect.objectContaining({ - filters: { ...filters, [name]: values[0] }, - }), - ); - }); - - await userEvent.click(button); - - await waitFor(() => { - expect(screen.getByRole('listbox')).toBeInTheDocument(); - }); - - await userEvent.click(screen.getByRole('option', { name: 'All' })); - - await waitFor(() => { - expect(query).toHaveBeenLastCalledWith( - expect.objectContaining({ filters }), - ); - }); - }); - }); -}); diff --git a/plugins/search/src/components/SearchFilter/SearchFilter.tsx b/plugins/search/src/components/SearchFilter/SearchFilter.tsx deleted file mode 100644 index e58bbd5c14..0000000000 --- a/plugins/search/src/components/SearchFilter/SearchFilter.tsx +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { ReactElement } from 'react'; - -import { - AutocompleteFilter, - CheckboxFilter, - SearchAutocompleteFilterProps, - SelectFilter, -} from '@backstage/plugin-search-react'; - -/** - * @public - * @deprecated Import from `@backstage/plugin-search-react` instead. - */ -export type SearchFilterComponentProps = { - className?: string; - name: string; - label?: string; - /** - * Either an array of values directly, or an async function to return a list - * of values to be used in the filter. In the autocomplete filter, the last - * input value is provided as an input to allow values to be filtered. This - * function is debounced and values cached. - */ - values?: string[] | ((partial: string) => Promise); - defaultValue?: string[] | string | null; - /** - * Debounce time in milliseconds, used when values is an async callback. - * Defaults to 250ms. - */ - valuesDebounceMs?: number; -}; - -/** - * @public - * @deprecated Import from `@backstage/plugin-search-react` instead. - */ -export type SearchFilterWrapperProps = SearchFilterComponentProps & { - component: (props: SearchFilterComponentProps) => ReactElement; - debug?: boolean; -}; - -/** - * @public - * @deprecated Import from `@backstage/plugin-search-react` instead. - */ -const SearchFilter = ({ - component: Element, - ...props -}: SearchFilterWrapperProps) => ; - -/** - * @deprecated Import from `@backstage/plugin-search-react` instead. - */ -SearchFilter.Checkbox = ( - props: Omit & - SearchFilterComponentProps, -) => ; - -/** - * @deprecated Import from `@backstage/plugin-search-react` instead. - */ -SearchFilter.Select = ( - props: Omit & - SearchFilterComponentProps, -) => ; - -/** - * A control surface for a given filter field name, rendered as an autocomplete - * textfield. A hard-coded list of values may be provided, or an async function - * which returns values may be provided instead. - * - * @public - * @deprecated Import from `@backstage/plugin-search-react` instead. - */ -SearchFilter.Autocomplete = (props: SearchAutocompleteFilterProps) => ( - -); - -export { SearchFilter }; diff --git a/plugins/search/src/components/SearchFilter/index.ts b/plugins/search/src/components/SearchFilter/index.ts deleted file mode 100644 index 3050330038..0000000000 --- a/plugins/search/src/components/SearchFilter/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { SearchFilter } from './SearchFilter'; -export type { - SearchFilterComponentProps, - SearchFilterWrapperProps, -} from './SearchFilter'; -export type { SearchAutocompleteFilterProps } from './SearchFilter.Autocomplete'; diff --git a/plugins/search/src/components/SearchModal/SearchModal.stories.tsx b/plugins/search/src/components/SearchModal/SearchModal.stories.tsx index aad018c334..eac01cceb8 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.stories.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.stories.tsx @@ -27,8 +27,8 @@ import { import { makeStyles } from '@material-ui/core/styles'; import React, { ComponentType } from 'react'; import { rootRouteRef } from '../../plugin'; -import { SearchBar } from '../SearchBar'; import { + SearchBar, DefaultResultListItem, searchApiRef, MockSearchApi, diff --git a/plugins/search/src/components/SearchPage/SearchPage.test.tsx b/plugins/search/src/components/SearchPage/SearchPage.test.tsx index 59e6e99aaf..459a541cbd 100644 --- a/plugins/search/src/components/SearchPage/SearchPage.test.tsx +++ b/plugins/search/src/components/SearchPage/SearchPage.test.tsx @@ -16,7 +16,7 @@ import { renderInTestApp } from '@backstage/test-utils'; import React from 'react'; -import { useLocation, useOutlet } from 'react-router'; +import { useLocation } from 'react-router'; import { useSearch } from '@backstage/plugin-search-react'; import { SearchPage } from './SearchPage'; @@ -50,11 +50,6 @@ jest.mock('@backstage/plugin-search-react', () => ({ }), })); -jest.mock('../LegacySearchPage', () => ({ - ...jest.requireActual('@backstage/plugin-search-react'), - LegacySearchPage: jest.fn().mockReturnValue('LegacySearchPageMock'), -})); - describe('SearchPage', () => { const origReplaceState = window.history.replaceState; @@ -96,13 +91,6 @@ describe('SearchPage', () => { expect(getByText('Route Children')).toBeInTheDocument(); }); - it('renders legacy search when no router children are provided', async () => { - (useOutlet as jest.Mock).mockReturnValueOnce(null); - const { getByText } = await renderInTestApp(); - - expect(getByText('LegacySearchPageMock')).toBeInTheDocument(); - }); - it('replaces window history with expected query parameters', async () => { (useSearch as jest.Mock).mockReturnValueOnce({ term: 'bieber', diff --git a/plugins/search/src/components/SearchPage/SearchPage.tsx b/plugins/search/src/components/SearchPage/SearchPage.tsx index 7145c7a5ef..f267918e12 100644 --- a/plugins/search/src/components/SearchPage/SearchPage.tsx +++ b/plugins/search/src/components/SearchPage/SearchPage.tsx @@ -23,7 +23,6 @@ import { useSearch, } from '@backstage/plugin-search-react'; import { JsonObject } from '@backstage/types'; -import { LegacySearchPage } from '../LegacySearchPage'; export const UrlUpdater = () => { const location = useLocation(); @@ -96,7 +95,7 @@ export const SearchPage = () => { return ( - {outlet || } + {outlet} ); }; diff --git a/plugins/search/src/components/SearchResultPager/SearchResultPager.test.tsx b/plugins/search/src/components/SearchResultPager/SearchResultPager.test.tsx deleted file mode 100644 index b1cad8d748..0000000000 --- a/plugins/search/src/components/SearchResultPager/SearchResultPager.test.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { renderInTestApp } from '@backstage/test-utils'; -import { waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; -import { useSearch } from '@backstage/plugin-search-react'; -import { SearchResultPager } from './SearchResultPager'; - -jest.mock('@backstage/plugin-search-react', () => ({ - ...jest.requireActual('@backstage/plugin-search-react'), - useSearch: jest.fn().mockReturnValue({ - result: {}, - }), -})); - -describe('SearchResultPager', () => { - it('renders pager buttons', async () => { - const fetchNextPage = jest.fn(); - const fetchPreviousPage = jest.fn(); - (useSearch as jest.Mock).mockReturnValueOnce({ - result: { loading: false, value: [] }, - fetchNextPage, - fetchPreviousPage, - }); - - const { getByLabelText } = await renderInTestApp(); - - await waitFor(() => { - expect(getByLabelText('previous page')).toBeInTheDocument(); - }); - await userEvent.click(getByLabelText('previous page')); - expect(fetchPreviousPage).toBeCalled(); - - await waitFor(() => { - expect(getByLabelText('next page')).toBeInTheDocument(); - }); - await userEvent.click(getByLabelText('next page')); - - expect(fetchNextPage).toBeCalled(); - }); -}); diff --git a/plugins/search/src/components/SearchResultPager/SearchResultPager.tsx b/plugins/search/src/components/SearchResultPager/SearchResultPager.tsx deleted file mode 100644 index 287e485dd1..0000000000 --- a/plugins/search/src/components/SearchResultPager/SearchResultPager.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Button, makeStyles } from '@material-ui/core'; -import ArrowBackIosIcon from '@material-ui/icons/ArrowBackIos'; -import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos'; -import React from 'react'; -import { useSearch } from '@backstage/plugin-search-react'; - -const useStyles = makeStyles(theme => ({ - root: { - display: 'flex', - justifyContent: 'space-between', - gap: theme.spacing(2), - margin: theme.spacing(2, 0), - }, -})); - -/** - * @public - * @deprecated Import from `@backstage/plugin-search-react` instead. - */ -export const SearchResultPager = () => { - const { fetchNextPage, fetchPreviousPage } = useSearch(); - const classes = useStyles(); - - if (!fetchNextPage && !fetchPreviousPage) { - return <>; - } - - return ( - - ); -}; diff --git a/plugins/search/src/components/SearchResultPager/index.ts b/plugins/search/src/components/SearchResultPager/index.ts deleted file mode 100644 index 39b72ec71f..0000000000 --- a/plugins/search/src/components/SearchResultPager/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { SearchResultPager } from './SearchResultPager'; diff --git a/plugins/search/src/index.ts b/plugins/search/src/index.ts index fd0e70c61a..8e1db7c2b8 100644 --- a/plugins/search/src/index.ts +++ b/plugins/search/src/index.ts @@ -20,25 +20,8 @@ * @packageDocumentation */ -export { Filters, FiltersButton } from './components/Filters'; -export type { - FilterOptions, - FiltersState, - FiltersProps, - FiltersButtonProps, -} from './components/Filters'; export type { HomePageSearchBarProps } from './components/HomePageComponent'; -export { SearchBar, SearchBarBase } from './components/SearchBar'; -export type { - SearchBarBaseProps, - SearchBarProps, -} from './components/SearchBar'; -export { SearchFilter } from './components/SearchFilter'; -export type { - SearchAutocompleteFilterProps, - SearchFilterComponentProps, - SearchFilterWrapperProps, -} from './components/SearchFilter'; + export { SearchModal, SearchModalProvider, @@ -51,7 +34,6 @@ export type { SearchModalValue, } from './components/SearchModal'; export { SearchPage as Router } from './components/SearchPage'; -export { SearchResultPager } from './components/SearchResultPager'; export { SearchType } from './components/SearchType'; export type { SearchTypeAccordionProps, @@ -63,11 +45,9 @@ export type { SidebarSearchProps } from './components/SidebarSearch'; export type { SidebarSearchModalProps } from './components/SidebarSearchModal'; export { - DefaultResultListItem, HomePageSearchBar, SearchPage, searchPlugin as plugin, searchPlugin, - SearchResult, SidebarSearchModal, } from './plugin'; diff --git a/plugins/search/src/plugin.ts b/plugins/search/src/plugin.ts index 19fd501129..07a645f73e 100644 --- a/plugins/search/src/plugin.ts +++ b/plugins/search/src/plugin.ts @@ -15,11 +15,7 @@ */ import { SearchClient } from './apis'; -import { - searchApiRef, - SearchResult as RealSearchResult, - DefaultResultListItem as RealDefaultResultListItem, -} from '@backstage/plugin-search-react'; +import { searchApiRef } from '@backstage/plugin-search-react'; import { createApiFactory, createPlugin, @@ -64,12 +60,6 @@ export const SearchPage = searchPlugin.provide( }), ); -/** - * @public - * @deprecated Import from `@backstage/plugin-search-react` instead. - */ -export const SearchResult = RealSearchResult; - /** * @public */ @@ -85,12 +75,6 @@ export const SidebarSearchModal = searchPlugin.provide( }), ); -/** - * @public - * @deprecated Import from `@backstage/plugin-search-react` instead. - */ -export const DefaultResultListItem = RealDefaultResultListItem; - /** * @public */