Making Search page composable.
Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
@@ -54,6 +54,7 @@ import { Navigate, Route } from 'react-router';
|
||||
import { apis } from './apis';
|
||||
import { Root } from './components/Root';
|
||||
import { entityPage } from './components/catalog/EntityPage';
|
||||
import { searchPage } from './components/search/SearchPage';
|
||||
import { providers } from './identityProviders';
|
||||
import * as plugins from './plugins';
|
||||
|
||||
@@ -121,8 +122,10 @@ const routes = (
|
||||
<Route path="/search" element={<SearchPage />} />
|
||||
<Route
|
||||
path="/search-next"
|
||||
element={<SearchPageNext /* TODO: illustrate customization */ />}
|
||||
/>
|
||||
element={<SearchPageNext />}
|
||||
>
|
||||
{searchPage}
|
||||
</Route>
|
||||
<Route path="/cost-insights" element={<CostInsightsPage />} />
|
||||
<Route
|
||||
path="/cost-insights/investigating-growth"
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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 { Content, Header, Lifecycle, Page } from '@backstage/core';
|
||||
import { Grid, List } from '@material-ui/core';
|
||||
import { SearchBarNext } from '@backstage/plugin-search';
|
||||
import { SearchResultNext, DefaultResultListItem } from '@backstage/plugin-search';
|
||||
import { SearchFiltersNext, FilterType } from '@backstage/plugin-search';
|
||||
import { CatalogResultListItem } from '@backstage/plugin-catalog';
|
||||
|
||||
const filterDefinitions = [
|
||||
{
|
||||
field: 'kind',
|
||||
type: FilterType.SELECT,
|
||||
values: ['Component', 'Template'],
|
||||
},
|
||||
{
|
||||
field: 'lifecycle',
|
||||
type: FilterType.CHECKBOX,
|
||||
values: ['experimental', 'production'],
|
||||
},
|
||||
];
|
||||
|
||||
export const searchPage = (
|
||||
<Page themeId="home">
|
||||
<Header title="Search" subtitle={<Lifecycle alpha />} />
|
||||
<Content>
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={12}>
|
||||
<SearchBarNext />
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<SearchFiltersNext definitions={filterDefinitions} />
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<SearchResultNext>
|
||||
{({results}) => (
|
||||
<List>
|
||||
{results.map(result => {
|
||||
switch (result.type) {
|
||||
case 'software-catalog':
|
||||
return (
|
||||
<CatalogResultListItem
|
||||
key={result.document.location}
|
||||
result={result.document}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<DefaultResultListItem
|
||||
key={result.document.location}
|
||||
result={result.document}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</List>
|
||||
)}
|
||||
</SearchResultNext>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
* l
|
||||
imitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Link } from '@backstage/core';
|
||||
import { Box, Chip, Divider, ListItem, ListItemText, makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
flexContainer: {
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
itemText: {
|
||||
width: '100%',
|
||||
marginBottom: '1rem',
|
||||
},
|
||||
});
|
||||
|
||||
export const CatalogResultListItem = ({ result }: any) => {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<Link to={result.location}>
|
||||
<ListItem alignItems="flex-start" className={classes.flexContainer}>
|
||||
<ListItemText
|
||||
className={classes.itemText}
|
||||
primaryTypographyProps={{ variant: 'h6' }}
|
||||
primary={result.title}
|
||||
secondary={result.text}
|
||||
/>
|
||||
<Box>
|
||||
{result.kind && (<Chip label={`Kind: ${result.kind}`} size="small" />)}
|
||||
{result.lifecycle && (<Chip label={`Lifecycle: ${result.lifecycle}`} size="small" />)}
|
||||
</Box>
|
||||
</ListItem>
|
||||
<Divider component="li" />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 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 { CatalogResultListItem } from './CatalogResultListItem';
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
export { AboutCard } from './components/AboutCard';
|
||||
export { CatalogResultListItem } from './components/CatalogResultListItem';
|
||||
export { EntityLayout } from './components/EntityLayout';
|
||||
export { EntityPageLayout } from './components/EntityPageLayout';
|
||||
export { CatalogTable } from './components/CatalogTable';
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"qs": "^6.9.4",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router": "6.0.0-beta.0",
|
||||
"react-router-dom": "6.0.0-beta.0",
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
* l
|
||||
imitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Link } from '@backstage/core';
|
||||
import { ListItem, ListItemText, Divider } from '@material-ui/core';
|
||||
|
||||
export const DefaultResultListItem = ({ result }: any) => {
|
||||
return (
|
||||
<Link to={result.location}>
|
||||
<ListItem alignItems="flex-start">
|
||||
<ListItemText
|
||||
primaryTypographyProps={{ variant: 'h6' }}
|
||||
primary={result.title}
|
||||
secondary={result.text}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider component="li" />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
* l
|
||||
imitations under the License.
|
||||
*/
|
||||
|
||||
export { DefaultResultListItem } from './DefaultResultListItem';
|
||||
@@ -15,45 +15,13 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Content, Header, Lifecycle, Page } from '@backstage/core';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import { SearchBarNext } from '../SearchBarNext';
|
||||
import { SearchResultNext } from '../SearchResultNext';
|
||||
import { Outlet } from 'react-router';
|
||||
import { SearchContextProvider } from '../SearchContext';
|
||||
import { SearchFiltersNext, FilterType } from '../SearchFiltersNext';
|
||||
|
||||
const defaultFilterDefinitions = [
|
||||
{
|
||||
field: 'kind',
|
||||
type: FilterType.SELECT,
|
||||
values: ['Component', 'Template'],
|
||||
},
|
||||
{
|
||||
field: 'lifecycle',
|
||||
type: FilterType.CHECKBOX,
|
||||
values: ['experimental', 'production'],
|
||||
},
|
||||
];
|
||||
|
||||
export const SearchPageNext = () => {
|
||||
return (
|
||||
<SearchContextProvider>
|
||||
<Page themeId="home">
|
||||
<Header title="Search" subtitle={<Lifecycle alpha />} />
|
||||
<Content>
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={12}>
|
||||
<SearchBarNext />
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<SearchFiltersNext definitions={defaultFilterDefinitions} />
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<SearchResultNext />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Content>
|
||||
</Page>
|
||||
<Outlet />
|
||||
</SearchContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,44 +13,18 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { EmptyState, Link, Progress } from '@backstage/core';
|
||||
import { Divider, List, ListItem, ListItemText } from '@material-ui/core';
|
||||
import { EmptyState, Progress } from '@backstage/core';
|
||||
import { SearchResult } from '@backstage/search-common';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import React from 'react';
|
||||
|
||||
import { useSearch } from '../SearchContext';
|
||||
|
||||
const DefaultResultListItem = ({ result }: any) => {
|
||||
return (
|
||||
<Link to={result.location}>
|
||||
<ListItem alignItems="flex-start">
|
||||
<ListItemText
|
||||
primaryTypographyProps={{ variant: 'h6' }}
|
||||
primary={result.title}
|
||||
secondary={result.text}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider component="li" />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
type ChildrenArguments = {
|
||||
results: SearchResult[];
|
||||
}
|
||||
|
||||
const TechDocsResultListItem = ({ result }: any) => {
|
||||
return (
|
||||
<Link to={result.location}>
|
||||
<ListItem alignItems="flex-start">
|
||||
<ListItemText
|
||||
primaryTypographyProps={{ variant: 'h6' }}
|
||||
primary={result.text}
|
||||
secondary={result.location}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider component="li" />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export const SearchResultNext = () => {
|
||||
export const SearchResultNext = ({ children }: { children: (results: ChildrenArguments) => JSX.Element}) => {
|
||||
const {
|
||||
result: { loading, error, value },
|
||||
} = useSearch();
|
||||
@@ -67,37 +41,8 @@ export const SearchResultNext = () => {
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
return <EmptyState missing="data" title="Sorry, no results were found" />;
|
||||
return <EmptyState missing="data" title="Sorry, no results were found" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<List>
|
||||
{value.results.map(result => {
|
||||
// Render different result items based on document type
|
||||
switch (result.type) {
|
||||
case 'software-catalog':
|
||||
return (
|
||||
<DefaultResultListItem
|
||||
key={result.document.location}
|
||||
result={result.document}
|
||||
/>
|
||||
);
|
||||
case 'techdocs':
|
||||
return (
|
||||
<TechDocsResultListItem
|
||||
key={result.document.location}
|
||||
result={result.document}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<DefaultResultListItem
|
||||
key={result.document.location}
|
||||
result={result.document}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</List>
|
||||
);
|
||||
return children({ results: value.results });
|
||||
};
|
||||
|
||||
@@ -22,5 +22,6 @@ export * from './SearchPage';
|
||||
export * from './SearchPageNext';
|
||||
export * from './SearchResult';
|
||||
export * from './SearchResultNext';
|
||||
export * from './DefaultResultListItem';
|
||||
export * from './SidebarSearch';
|
||||
export * from './SearchContext';
|
||||
|
||||
@@ -23,11 +23,15 @@ export {
|
||||
SearchBarNext,
|
||||
SearchResultNext,
|
||||
SearchFiltersNext,
|
||||
DefaultResultListItem,
|
||||
} from './plugin';
|
||||
export {
|
||||
Filters,
|
||||
FiltersButton,
|
||||
FilterType,
|
||||
SearchBar,
|
||||
SearchContextProvider,
|
||||
useSearch,
|
||||
SearchPage as Router,
|
||||
SearchResult,
|
||||
SidebarSearch,
|
||||
|
||||
@@ -90,6 +90,15 @@ export const SearchResultNext = searchPlugin.provide(
|
||||
}),
|
||||
);
|
||||
|
||||
export const DefaultResultListItem= searchPlugin.provide(
|
||||
createComponentExtension({
|
||||
component: {
|
||||
lazy: () =>
|
||||
import('./components/DefaultResultListItem').then(m => m.DefaultResultListItem),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export const SearchFiltersNext = searchPlugin.provide(
|
||||
createComponentExtension({
|
||||
component: {
|
||||
|
||||
Reference in New Issue
Block a user