[Search] Search frontend plugin (#3086)
* feat(search) search plugin * feat(search): search page components * feat(search): api * feat(search): filters wip * feat(search): wip filters * fix(search): delete unused useParams hook * fix(search): update docs * fix(search): use latest versions of dependencies * fix(search): change version of catalog plugin * fix(search api): pass instance of catalog api to search api * fix(filters): rename component from FilterButton to FiltersButton * fixup * fix(filters): use list of checkboxes to match style of catalog page filters * fix(styles): use theme spacing for margins and paddings, delete unused styles * fix(search): change terminology of search input to be more consistent and clear * fix(search): restructure component exports according to ADR * fix(search): replace sm with xs on Grid components to support smaller screens * fix(search): add types * fixup * fix(search): bump backstage core * change versions of backstage theme and dev-utils
This commit is contained in:
@@ -9,5 +9,6 @@
|
||||
/plugins/cost-insights @backstage/silver-lining
|
||||
/plugins/cloudbuild @trivago/ebarrios
|
||||
/plugins/techdocs @backstage/techdocs-core
|
||||
/plugins/search @backstage/techdocs-core
|
||||
/plugins/techdocs-backend @backstage/techdocs-core
|
||||
/.changeset/cost-insights-* @backstage/silver-lining
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"@backstage/plugin-rollbar": "^0.2.1",
|
||||
"@backstage/plugin-scaffolder": "^0.3.0",
|
||||
"@backstage/plugin-sentry": "^0.2.1",
|
||||
"@backstage/plugin-search": "^0.2.0",
|
||||
"@backstage/plugin-tech-radar": "^0.3.0",
|
||||
"@backstage/plugin-techdocs": "^0.2.1",
|
||||
"@backstage/plugin-user-settings": "^0.2.1",
|
||||
|
||||
@@ -39,3 +39,4 @@ export { plugin as CostInsights } from '@backstage/plugin-cost-insights';
|
||||
export { plugin as GitHubInsights } from '@roadiehq/backstage-plugin-github-insights';
|
||||
export { plugin as UserSettings } from '@backstage/plugin-user-settings';
|
||||
export { plugin as BuildKite } from '@roadiehq/backstage-plugin-buildkite';
|
||||
export { plugin as Search } from '@backstage/plugin-search';
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint')],
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
# Backstage Search
|
||||
|
||||
**This plugin is still under development.**
|
||||
|
||||
You can follow the progress under the Global search in Backstage [milestone](https://github.com/backstage/backstage/milestone/21) or reach out to us in the #search Discord channel.
|
||||
|
||||
## Getting started
|
||||
|
||||
Run `yarn start` in the root directory, and then navigate to [/search](http://localhost:3000/search)to check out the plugin.
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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 { createDevApp } from '@backstage/dev-utils';
|
||||
import { plugin } from '../src/plugin';
|
||||
|
||||
createDevApp().registerPlugin(plugin).render();
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "@backstage/plugin-search",
|
||||
"version": "0.2.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "backstage-cli plugin:build",
|
||||
"start": "backstage-cli plugin:serve",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"diff": "backstage-cli plugin:diff",
|
||||
"prepack": "backstage-cli prepack",
|
||||
"postpack": "backstage-cli postpack",
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.3.0",
|
||||
"@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.0",
|
||||
"react-router-dom": "6.0.0-beta.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.2.0",
|
||||
"@backstage/dev-utils": "^0.1.3",
|
||||
"@backstage/test-utils": "^0.1.2",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
"@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"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 { CatalogApi } from '@backstage/plugin-catalog';
|
||||
|
||||
export type Result = {
|
||||
name: string;
|
||||
description: string;
|
||||
owner: string;
|
||||
kind: string;
|
||||
lifecycle: string;
|
||||
};
|
||||
|
||||
export type SearchResults = Array<Result>;
|
||||
|
||||
class SearchApi {
|
||||
private catalogApi: CatalogApi;
|
||||
|
||||
constructor(catalogApi: CatalogApi) {
|
||||
this.catalogApi = catalogApi;
|
||||
}
|
||||
|
||||
private async entities() {
|
||||
const entities = await this.catalogApi.getEntities();
|
||||
return entities.map((result: any) => ({
|
||||
name: result.metadata.name,
|
||||
description: result.metadata.description,
|
||||
owner: result.spec.owner,
|
||||
kind: result.kind,
|
||||
lifecycle: result.spec.lifecycle,
|
||||
}));
|
||||
}
|
||||
|
||||
public getSearchResult(): Promise<SearchResults> {
|
||||
return this.entities();
|
||||
}
|
||||
}
|
||||
|
||||
export default SearchApi;
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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 {
|
||||
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<string>;
|
||||
};
|
||||
|
||||
type FiltersProps = {
|
||||
filters: FiltersState;
|
||||
resetFilters: () => void;
|
||||
updateSelected: (filter: string) => void;
|
||||
updateChecked: (filter: string) => void;
|
||||
};
|
||||
|
||||
export const Filters = ({
|
||||
filters,
|
||||
resetFilters,
|
||||
updateSelected,
|
||||
updateChecked,
|
||||
}: FiltersProps) => {
|
||||
const classes = useStyles();
|
||||
|
||||
// TODO: move mocked filters out of filters component to make it more generic
|
||||
const filter1 = ['All', 'API', 'Component', 'Location', 'Template'];
|
||||
const filter2 = ['deprecated', 'recommended', 'experimental', 'production'];
|
||||
|
||||
return (
|
||||
<Card className={classes.filters}>
|
||||
<CardHeader
|
||||
title={<Typography variant="h6">Filters</Typography>}
|
||||
action={
|
||||
<Button color="primary" onClick={() => resetFilters()}>
|
||||
CLEAR ALL
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<Typography variant="subtitle2">Kind</Typography>
|
||||
<Select
|
||||
id="outlined-select"
|
||||
onChange={(e: React.ChangeEvent<any>) =>
|
||||
updateSelected(e?.target?.value)
|
||||
}
|
||||
variant="outlined"
|
||||
className={classes.dropdown}
|
||||
value={filters.selected}
|
||||
>
|
||||
{filter1.map(filter => (
|
||||
<MenuItem
|
||||
selected={filter === 'All'}
|
||||
dense
|
||||
key={filter}
|
||||
value={filter}
|
||||
>
|
||||
{filter}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</CardContent>
|
||||
<CardContent>
|
||||
<Typography variant="subtitle2">Lifecycle</Typography>
|
||||
<List disablePadding dense>
|
||||
{filter2.map(filter => (
|
||||
<ListItem
|
||||
key={filter}
|
||||
dense
|
||||
button
|
||||
onClick={() => updateChecked(filter)}
|
||||
>
|
||||
<Checkbox
|
||||
edge="start"
|
||||
disableRipple
|
||||
className={classes.checkbox}
|
||||
color="primary"
|
||||
checked={filters.checked.includes(filter)}
|
||||
tabIndex={-1}
|
||||
value={filter}
|
||||
name={filter}
|
||||
/>
|
||||
<ListItemText id={filter} primary={filter} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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 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 (
|
||||
<div className={classes.filters}>
|
||||
<IconButton
|
||||
className={classes.icon}
|
||||
aria-label="settings"
|
||||
onClick={handleToggleFilters}
|
||||
>
|
||||
<FilterListIcon />
|
||||
</IconButton>
|
||||
<Typography variant="h6">
|
||||
Filters ({numberOfSelectedFilters ? numberOfSelectedFilters : 0})
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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 { FiltersButton } from './FiltersButton';
|
||||
export { Filters } from './Filters';
|
||||
export type { FiltersState } from './Filters';
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 { 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 (
|
||||
<Paper
|
||||
component="form"
|
||||
onSubmit={e => handleSearch(e)}
|
||||
className={classes.root}
|
||||
>
|
||||
<IconButton disabled type="submit" aria-label="search">
|
||||
<SearchIcon />
|
||||
</IconButton>
|
||||
<InputBase
|
||||
className={classes.input}
|
||||
placeholder="Search in Backstage"
|
||||
value={searchQuery}
|
||||
onChange={e => handleSearch(e)}
|
||||
inputProps={{ 'aria-label': 'search backstage' }}
|
||||
/>
|
||||
<IconButton aria-label="search" onClick={() => handleClearSearchBar()}>
|
||||
<ClearButton />
|
||||
</IconButton>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 { SearchBar } from './SearchBar';
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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, { useState } from 'react';
|
||||
|
||||
import { Header, Content, Page } from '@backstage/core';
|
||||
import { Grid } from '@material-ui/core';
|
||||
|
||||
import { SearchBar } from '../SearchBar';
|
||||
import { SearchResult } from '../SearchResult';
|
||||
|
||||
export const SearchPage = () => {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
setSearchQuery(event.target.value);
|
||||
};
|
||||
|
||||
const handleClearSearchBar = () => {
|
||||
setSearchQuery('');
|
||||
};
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header title="Search" />
|
||||
<Content>
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={12}>
|
||||
<SearchBar
|
||||
handleSearch={handleSearch}
|
||||
handleClearSearchBar={handleClearSearchBar}
|
||||
searchQuery={searchQuery}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<SearchResult searchQuery={searchQuery.toLowerCase()} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 { SearchPage } from './SearchPage';
|
||||
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* 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, { useState, useEffect } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { makeStyles, Typography, Grid, Divider } from '@material-ui/core';
|
||||
import { Table, TableColumn, useApi } from '@backstage/core';
|
||||
import { catalogApiRef } from '@backstage/plugin-catalog';
|
||||
|
||||
import { FiltersButton, Filters, FiltersState } from '../Filters';
|
||||
import SearchApi, { Result, SearchResults } from '../../apis';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
searchTerm: {
|
||||
background: '#eee',
|
||||
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;
|
||||
};
|
||||
|
||||
type Filters = {
|
||||
selected: string;
|
||||
checked: Array<string | null>;
|
||||
};
|
||||
|
||||
// TODO: move out column to make the search result component more generic
|
||||
const columns: TableColumn[] = [
|
||||
{
|
||||
title: 'Component Id',
|
||||
field: 'name',
|
||||
highlight: true,
|
||||
},
|
||||
{
|
||||
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 (
|
||||
<div className={classes.tableHeader}>
|
||||
<FiltersButton
|
||||
numberOfSelectedFilters={numberOfSelectedFilters}
|
||||
handleToggleFilters={handleToggleFilters}
|
||||
/>
|
||||
<Divider className={classes.divider} orientation="vertical" />
|
||||
<Grid item xs={12}>
|
||||
{searchQuery ? (
|
||||
<Typography variant="h6">
|
||||
{`${numberOfResults} `}
|
||||
{numberOfResults > 1 ? `results for ` : `result for `}
|
||||
<span className={classes.searchTerm}>"{searchQuery}"</span>{' '}
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography variant="h6">{`${numberOfResults} results`}</Typography>
|
||||
)}
|
||||
</Grid>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const SearchResult = ({ searchQuery }: SearchResultProps) => {
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
|
||||
const [showFilters, toggleFilters] = useState(false);
|
||||
const [filters, setFilters] = useState<FiltersState>({
|
||||
selected: 'All',
|
||||
checked: [],
|
||||
});
|
||||
|
||||
const [filteredResults, setFilteredResults] = useState<SearchResults>([]);
|
||||
|
||||
const searchApi = new SearchApi(catalogApi);
|
||||
|
||||
const { loading, error, value: results } = useAsync(() => {
|
||||
return searchApi.getSearchResult();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (results) {
|
||||
let withFilters = results;
|
||||
|
||||
// apply filters
|
||||
|
||||
// filter on selected
|
||||
if (filters.selected !== 'All') {
|
||||
withFilters = results.filter((result: Result) =>
|
||||
filters.selected.includes(result.kind),
|
||||
);
|
||||
}
|
||||
|
||||
// filter on checked
|
||||
if (filters.checked.length > 0) {
|
||||
withFilters = withFilters.filter((result: Result) =>
|
||||
filters.checked.includes(result.lifecycle),
|
||||
);
|
||||
}
|
||||
|
||||
// filter on searchQuery
|
||||
if (searchQuery) {
|
||||
withFilters = withFilters.filter(
|
||||
(result: Result) =>
|
||||
result.name?.toLowerCase().includes(searchQuery) ||
|
||||
result.description?.toLowerCase().includes(searchQuery),
|
||||
);
|
||||
}
|
||||
|
||||
setFilteredResults(withFilters);
|
||||
}
|
||||
}, [filters, searchQuery, results]);
|
||||
|
||||
if (loading || error || !results) return null;
|
||||
|
||||
const resetFilters = () => {
|
||||
setFilters({
|
||||
selected: 'All',
|
||||
checked: [],
|
||||
});
|
||||
};
|
||||
|
||||
const updateSelected = (filter: string) => {
|
||||
setFilters(prevState => ({
|
||||
...prevState,
|
||||
selected: filter,
|
||||
}));
|
||||
};
|
||||
|
||||
const updateChecked = (filter: string) => {
|
||||
if (filters.checked.includes(filter)) {
|
||||
setFilters(prevState => ({
|
||||
...prevState,
|
||||
checked: prevState.checked.filter(item => item !== filter),
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
setFilters(prevState => ({
|
||||
...prevState,
|
||||
checked: [...prevState.checked, filter],
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid container>
|
||||
{showFilters && (
|
||||
<Grid item xs={3}>
|
||||
<Filters
|
||||
filters={filters}
|
||||
resetFilters={resetFilters}
|
||||
updateSelected={updateSelected}
|
||||
updateChecked={updateChecked}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={showFilters ? 9 : 12}>
|
||||
<Table
|
||||
options={{ paging: true, search: false }}
|
||||
data={filteredResults}
|
||||
columns={columns}
|
||||
title={
|
||||
<TableHeader
|
||||
searchQuery={searchQuery}
|
||||
numberOfResults={filteredResults.length}
|
||||
numberOfSelectedFilters={
|
||||
(filters.selected !== 'All' ? 1 : 0) + filters.checked.length
|
||||
}
|
||||
handleToggleFilters={() => toggleFilters(!showFilters)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 { SearchResult } from './SearchResult';
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 * from './Filters';
|
||||
export * from './SearchBar';
|
||||
export * from './SearchPage';
|
||||
export * from './SearchResult';
|
||||
@@ -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 { plugin } from './plugin';
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { plugin } from './plugin';
|
||||
|
||||
describe('search', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(plugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 { createPlugin, createRouteRef } from '@backstage/core';
|
||||
import { SearchPage } from './components/SearchPage';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
path: '/search',
|
||||
title: 'search',
|
||||
});
|
||||
|
||||
export const plugin = createPlugin({
|
||||
id: 'search',
|
||||
register({ router }) {
|
||||
router.addRoute(rootRouteRef, SearchPage);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 '@testing-library/jest-dom';
|
||||
import 'cross-fetch/polyfill';
|
||||
Reference in New Issue
Block a user