From ab548091ed21fc8e1272711fec2dbdc2c3bd644c Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 4 Jun 2021 17:41:34 +0200 Subject: [PATCH] -> , with upgrade instructions and deprecation warnings. Signed-off-by: Eric Peterson --- packages/app/src/App.tsx | 5 +- .../SearchPage.test.tsx} | 21 ++-- .../src/components/SearchPage/SearchPage.tsx | 110 +++++++++++------- .../SearchPageNext/SearchPageNext.tsx | 67 ----------- .../src/components/SearchPageNext/index.tsx | 17 --- plugins/search/src/components/index.tsx | 1 - plugins/search/src/plugin.ts | 15 ++- 7 files changed, 91 insertions(+), 145 deletions(-) rename plugins/search/src/components/{SearchPageNext/SearchPageNext.test.tsx => SearchPage/SearchPage.test.tsx} (83%) delete mode 100644 plugins/search/src/components/SearchPageNext/SearchPageNext.tsx delete mode 100644 plugins/search/src/components/SearchPageNext/index.tsx diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 5874465ef6..b14f9a3163 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -43,7 +43,7 @@ import { GraphiQLPage } from '@backstage/plugin-graphiql'; import { LighthousePage } from '@backstage/plugin-lighthouse'; import { NewRelicPage } from '@backstage/plugin-newrelic'; import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { SearchPage, SearchPageNext } from '@backstage/plugin-search'; +import { SearchPage } from '@backstage/plugin-search'; import { TechRadarPage } from '@backstage/plugin-tech-radar'; import { TechdocsPage } from '@backstage/plugin-techdocs'; import { UserSettingsPage } from '@backstage/plugin-user-settings'; @@ -119,8 +119,7 @@ const routes = ( } /> } /> } /> - } /> - }> + }> {searchPage} } /> diff --git a/plugins/search/src/components/SearchPageNext/SearchPageNext.test.tsx b/plugins/search/src/components/SearchPage/SearchPage.test.tsx similarity index 83% rename from plugins/search/src/components/SearchPageNext/SearchPageNext.test.tsx rename to plugins/search/src/components/SearchPage/SearchPage.test.tsx index 5890f8cf41..20bfb707cf 100644 --- a/plugins/search/src/components/SearchPageNext/SearchPageNext.test.tsx +++ b/plugins/search/src/components/SearchPage/SearchPage.test.tsx @@ -16,17 +16,17 @@ import React from 'react'; import { renderInTestApp } from '@backstage/test-utils'; -import { useLocation, Outlet } from 'react-router'; +import { useLocation, useOutlet } from 'react-router'; import { useSearch, SearchContextProvider } from '../SearchContext'; -import { SearchPageNext } from './'; +import { SearchPage } from './'; jest.mock('react-router', () => ({ ...jest.requireActual('react-router'), useLocation: jest.fn().mockReturnValue({ search: '', }), - Outlet: jest.fn().mockReturnValue(null), + useOutlet: jest.fn().mockReturnValue('Route Children'), })); jest.mock('../SearchContext', () => ({ @@ -68,7 +68,7 @@ describe('SearchPage', () => { }); // When we render the page... - await renderInTestApp(); + await renderInTestApp(); // Then search context should be initialized with these values... const calls = (SearchContextProvider as jest.Mock).mock.calls[0]; @@ -80,9 +80,16 @@ describe('SearchPage', () => { }); it('renders provided router element', async () => { - await renderInTestApp(); + const { getByText } = await renderInTestApp(); - expect(Outlet).toHaveBeenCalled(); + expect(getByText('Route Children')).toBeInTheDocument(); + }); + + it('renders upgrade error whe no router children are provided', async () => { + (useOutlet as jest.Mock).mockReturnValueOnce(null); + const { getByText } = await renderInTestApp(); + + expect(getByText('Error: No Search Layout Found')).toBeInTheDocument(); }); it('replaces window history with expected query parameters', async () => { @@ -96,7 +103,7 @@ describe('SearchPage', () => { '?query=bieber&types[]=software-catalog&pageCursor=page2-or-something&filters[anyKey]=anyValue', ); - await renderInTestApp(); + await renderInTestApp(); const calls = (window.history.replaceState as jest.Mock).mock.calls[0]; expect(calls[2]).toContain(expectedLocation); diff --git a/plugins/search/src/components/SearchPage/SearchPage.tsx b/plugins/search/src/components/SearchPage/SearchPage.tsx index 7c216fc908..5eb84c689c 100644 --- a/plugins/search/src/components/SearchPage/SearchPage.tsx +++ b/plugins/search/src/components/SearchPage/SearchPage.tsx @@ -13,55 +13,81 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Content, Header, Page, useQueryParamState } from '@backstage/core'; -import { Grid } from '@material-ui/core'; -import React, { useEffect, useState } from 'react'; -import { useDebounce } from 'react-use'; -import { SearchBar } from '../SearchBar'; -import { SearchResult } from '../SearchResult'; -export const SearchPage = () => { - 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(''); - }; +import React from 'react'; +import qs from 'qs'; +import { useLocation, useOutlet } from 'react-router'; +import { SearchContextProvider, useSearch } from '../SearchContext'; +import { JsonObject } from '@backstage/config'; +import { Content, Header, Page, Link, WarningPanel } from '@backstage/core'; +const UpdateInstructions = () => { return (
- - - - - - - - + + As of v0.4.0 of the Backstage Search Plugin, a search layout must + be provided by the App. For detailed instructions, check the{' '} + + getting started guide + + . + + } + /> ); }; + +export const UrlUpdater = () => { + const { term, types, pageCursor, filters } = useSearch(); + + const newParams = qs.stringify( + { + query: term, + types, + pageCursor, + filters, + }, + { arrayFormat: 'brackets' }, + ); + const newUrl = `${window.location.pathname}?${newParams}`; + + // We directly manipulate window history here in order to not re-render + // infinitely (state => location => state => etc). The intention of this + // code is just to ensure the right query/filters are loaded when a user + // clicks the "back" button after clicking a result. + window.history.replaceState(null, document.title, newUrl); + + return null; +}; + +export const SearchPage = () => { + const location = useLocation(); + const outlet = useOutlet(); + const query = qs.parse(location.search.substring(1), { arrayLimit: 0 }) || {}; + const filters = (query.filters as JsonObject) || {}; + const queryString = (query.query as string) || ''; + const pageCursor = (query.pageCursor as string) || ''; + const types = (query.types as string[]) || []; + + const initialState = { + term: queryString || '', + types, + pageCursor, + filters, + }; + + return ( + + + {outlet || } + + ); +}; diff --git a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx deleted file mode 100644 index f851778ddf..0000000000 --- a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 qs from 'qs'; -import { Outlet, useLocation } from 'react-router'; -import { SearchContextProvider, useSearch } from '../SearchContext'; -import { JsonObject } from '@backstage/config'; - -export const UrlUpdater = () => { - const { term, types, pageCursor, filters } = useSearch(); - - const newParams = qs.stringify( - { - query: term, - types, - pageCursor, - filters, - }, - { arrayFormat: 'brackets' }, - ); - const newUrl = `${window.location.pathname}?${newParams}`; - - // We directly manipulate window history here in order to not re-render - // infinitely (state => location => state => etc). The intention of this - // code is just to ensure the right query/filters are loaded when a user - // clicks the "back" button after clicking a result. - window.history.replaceState(null, document.title, newUrl); - - return null; -}; - -export const SearchPageNext = () => { - const location = useLocation(); - const query = qs.parse(location.search.substring(1), { arrayLimit: 0 }) || {}; - const filters = (query.filters as JsonObject) || {}; - const queryString = (query.query as string) || ''; - const pageCursor = (query.pageCursor as string) || ''; - const types = (query.types as string[]) || []; - - const initialState = { - term: queryString || '', - types, - pageCursor, - filters, - }; - - return ( - - - - - ); -}; diff --git a/plugins/search/src/components/SearchPageNext/index.tsx b/plugins/search/src/components/SearchPageNext/index.tsx deleted file mode 100644 index 464ba28750..0000000000 --- a/plugins/search/src/components/SearchPageNext/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 { SearchPageNext } from './SearchPageNext'; diff --git a/plugins/search/src/components/index.tsx b/plugins/search/src/components/index.tsx index 92e24b60df..40d5c3603d 100644 --- a/plugins/search/src/components/index.tsx +++ b/plugins/search/src/components/index.tsx @@ -19,7 +19,6 @@ export * from './SearchFilterNext'; export * from './SearchBar'; export * from './SearchBarNext'; export * from './SearchPage'; -export * from './SearchPageNext'; export * from './SearchResult'; export * from './SearchResultNext'; export * from './DefaultResultListItem'; diff --git a/plugins/search/src/plugin.ts b/plugins/search/src/plugin.ts index 6ff40ebaf7..01abf38df9 100644 --- a/plugins/search/src/plugin.ts +++ b/plugins/search/src/plugin.ts @@ -23,8 +23,6 @@ import { } from '@backstage/core'; import { SearchClient, searchApiRef } from './apis'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; -import { SearchPage as SearchPageComponent } from './components/SearchPage'; -import { SearchPageNext as SearchPageNextComponent } from './components/SearchPageNext'; export const rootRouteRef = createRouteRef({ path: '/search', @@ -47,10 +45,6 @@ export const searchPlugin = createPlugin({ }, }), ], - register({ router }) { - router.addRoute(rootRouteRef, SearchPageComponent); - router.addRoute(rootNextRouteRef, SearchPageNextComponent); - }, routes: { root: rootRouteRef, nextRoot: rootNextRouteRef, @@ -64,10 +58,15 @@ export const SearchPage = searchPlugin.provide( }), ); +/** + * @deprecated This component was used for rapid prototyping of the Backstage + * Search platform. Now that the API has stabilized, you should use the + * component instead. This component will be removed in an + * upcoming release. + */ export const SearchPageNext = searchPlugin.provide( createRoutableExtension({ - component: () => - import('./components/SearchPageNext').then(m => m.SearchPageNext), + component: () => import('./components/SearchPage').then(m => m.SearchPage), mountPoint: rootNextRouteRef, }), );