Make sidebar search field work (#3362)

* Make sidebar search field work

Extend the search page to have the ability to react to query parameters. The search in the sidebar now navigates to the search page and passes the query parameter. The search box on the search page is now debounced.

Closes #3341

* Fix sidebar search while the search page is already open
This commit is contained in:
Oliver Sand
2020-11-20 15:14:18 +01:00
committed by GitHub
parent 1550cba47b
commit 475fc0aaa3
11 changed files with 99 additions and 19 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core': patch
---
Clear sidebar search field once a search is executed
+6
View File
@@ -0,0 +1,6 @@
---
'example-app': patch
'@backstage/plugin-search': patch
---
Using the search field in the sidebar now navigates to the search result page.
+2 -8
View File
@@ -33,12 +33,12 @@ import {
SidebarContext,
SidebarItem,
SidebarDivider,
SidebarSearchField,
SidebarSpace,
} from '@backstage/core';
import { NavLink } from 'react-router-dom';
import { graphiQLRouteRef } from '@backstage/plugin-graphiql';
import { Settings as SidebarSettings } from '@backstage/plugin-user-settings';
import { SidebarSearch } from '@backstage/plugin-search';
const useSidebarLogoStyles = makeStyles({
root: {
@@ -73,17 +73,11 @@ const SidebarLogo: FC<{}> = () => {
);
};
const handleSearch = (query: string): void => {
// XXX (@koroeskohr): for testing purposes
// eslint-disable-next-line no-console
console.log(query);
};
const Root: FC<{}> = ({ children }) => (
<SidebarPage>
<Sidebar>
<SidebarLogo />
<SidebarSearchField onSearch={handleSearch} />
<SidebarSearch />
<SidebarDivider />
{/* Global nav, not org-specific */}
<SidebarItem icon={HomeIcon} to="/catalog" text="Home" />
+10 -1
View File
@@ -14,8 +14,9 @@
* limitations under the License.
*/
import { isEqual } from 'lodash';
import qs from 'qs';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { useDebounce } from 'react-use';
@@ -64,6 +65,14 @@ export function useQueryParamState<T>(
extractState(location.search, stateName),
);
useEffect(() => {
const newState = extractState(location.search, stateName);
setQueryParamState(oldState =>
isEqual(newState, oldState) ? oldState : newState,
);
}, [location, stateName]);
useDebounce(
() => {
const queryString = joinQueryString(
@@ -221,6 +221,7 @@ export const SidebarSearchField: FC<SidebarSearchFieldProps> = props => {
const handleEnter: KeyboardEventHandler = ev => {
if (ev.key === 'Enter') {
props.onSearch(input);
setInput('');
}
};
@@ -233,6 +234,7 @@ export const SidebarSearchField: FC<SidebarSearchFieldProps> = props => {
<SidebarItem icon={SearchIcon}>
<TextField
placeholder="Search"
value={input}
onChange={handleInput}
onKeyDown={handleEnter}
className={classes.searchContainer}
+5 -4
View File
@@ -21,14 +21,15 @@
},
"dependencies": {
"@backstage/core": "^0.3.1",
"@backstage/plugin-catalog": "^0.2.2",
"@backstage/theme": "^0.2.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@backstage/plugin-catalog": "^0.2.2",
"react-router-dom": "6.0.0-beta.0",
"qs": "^6.9.4",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
},
"devDependencies": {
@@ -40,8 +41,8 @@
"@testing-library/user-event": "^12.0.7",
"@types/jest": "^26.0.7",
"@types/node": "^12.0.0",
"msw": "^0.21.2",
"cross-fetch": "^3.0.6"
"cross-fetch": "^3.0.6",
"msw": "^0.21.2"
},
"files": [
"dist"
@@ -13,22 +13,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useState } from 'react';
import { Header, Content, Page } from '@backstage/core';
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 [searchQuery, setSearchQuery] = useState('');
const [queryString, setQueryString] = useQueryParamState<string>('query');
const [searchQuery, setSearchQuery] = useState(queryString ?? '');
const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault();
setSearchQuery(event.target.value);
};
useEffect(() => setSearchQuery(queryString ?? ''), [queryString]);
useDebounce(
() => {
setQueryString(searchQuery);
},
200,
[searchQuery],
);
const handleClearSearchBar = () => {
setSearchQuery('');
};
@@ -46,7 +56,7 @@ export const SearchPage = () => {
/>
</Grid>
<Grid item xs={12}>
<SearchResult searchQuery={searchQuery.toLowerCase()} />
<SearchResult searchQuery={(queryString ?? '').toLowerCase()} />
</Grid>
</Grid>
</Content>
@@ -0,0 +1,35 @@
/*
* 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, { useCallback } from 'react';
import qs from 'qs';
import { useNavigate } from 'react-router-dom';
import { SidebarSearchField } from '@backstage/core';
export const SidebarSearch = () => {
const navigate = useNavigate();
const handleSearch = useCallback(
(query: string): void => {
const queryString = qs.stringify({ query }, { addQueryPrefix: true });
// TODO: Here the url to the search plugin is hardcoded. We need a way to query the route in the future.
// Maybe an API that I can just call from other places?
navigate(`/search${queryString}`);
},
[navigate],
);
return <SidebarSearchField onSearch={handleSearch} />;
};
@@ -0,0 +1,16 @@
/*
* 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 { SidebarSearch } from './SidebarSearch';
+1
View File
@@ -18,3 +18,4 @@ export * from './Filters';
export * from './SearchBar';
export * from './SearchPage';
export * from './SearchResult';
export * from './SidebarSearch';
+1
View File
@@ -14,3 +14,4 @@
* limitations under the License.
*/
export { plugin } from './plugin';
export * from './components';