2020-07-03 00:02:49 +02:00
|
|
|
import React from 'react';
|
2020-07-02 12:13:51 +02:00
|
|
|
|
|
|
|
import Typography from '@material-ui/core/Typography';
|
|
|
|
import TextField from '@material-ui/core/TextField';
|
2020-07-08 00:30:17 +02:00
|
|
|
import MenuItem from '@material-ui/core/MenuItem';
|
2020-07-02 12:13:51 +02:00
|
|
|
import Container from '@material-ui/core/Container';
|
2020-07-08 00:30:17 +02:00
|
|
|
import Grid from '@material-ui/core/Grid';
|
2020-07-02 23:55:56 +02:00
|
|
|
import Pagination from '@material-ui/lab/Pagination';
|
2020-07-09 14:26:53 +02:00
|
|
|
import Box from '@material-ui/core/Box';
|
2020-07-02 12:13:51 +02:00
|
|
|
|
2020-07-09 14:26:53 +02:00
|
|
|
import useStyles from './SearchPage.style';
|
2020-07-02 12:13:51 +02:00
|
|
|
import { useSearch } from './utils/useSearch';
|
2021-06-08 13:55:27 +02:00
|
|
|
import { useFetch } from './utils/useFetch';
|
2020-07-02 23:55:56 +02:00
|
|
|
import {
|
2021-01-26 22:10:28 +01:00
|
|
|
SearchParamSetter,
|
2020-07-02 23:55:56 +02:00
|
|
|
useLocationSearch,
|
|
|
|
useLocationSearchState
|
|
|
|
} from './utils/routing';
|
2020-07-02 12:13:51 +02:00
|
|
|
import { SearchHit } from './SearchHit';
|
2021-06-08 13:55:27 +02:00
|
|
|
import { IndexAggregates } from './types/IndexStore'
|
2020-07-02 12:13:51 +02:00
|
|
|
|
|
|
|
export const SearchPage = () => {
|
2021-06-08 18:32:38 +02:00
|
|
|
document.title = "Search"
|
2021-09-16 13:37:14 +02:00
|
|
|
|
2020-07-09 14:26:53 +02:00
|
|
|
const classes = useStyles();
|
2021-06-08 13:55:27 +02:00
|
|
|
|
2020-07-02 23:55:56 +02:00
|
|
|
const pageSize = 20;
|
|
|
|
const [query, setQuery] = useLocationSearchState('query', '');
|
2020-07-08 00:30:17 +02:00
|
|
|
|
2021-06-08 13:55:27 +02:00
|
|
|
const [ruleType, setRuleType] = useLocationSearchState('types', 'ANY');
|
2021-01-26 22:10:28 +01:00
|
|
|
const allRuleTypes: Record<string,string> = {
|
|
|
|
'BUG': 'Bug',
|
2021-06-08 13:55:27 +02:00
|
|
|
'CODE_SMELL': 'Code Smell',
|
2021-01-26 22:10:28 +01:00
|
|
|
'SECURITY_HOTSPOT': 'Security Hotspot',
|
|
|
|
'VULNERABILITY': 'Vulnerability'
|
|
|
|
};
|
2020-07-08 00:30:17 +02:00
|
|
|
|
2021-01-26 22:10:28 +01:00
|
|
|
const [ruleTags, setRuleTags] = useLocationSearchState<string[]>('tags', [], value => value ? value.split(',') : []);
|
2021-06-08 13:55:27 +02:00
|
|
|
const [qualityProfiles, setQualityProfiles] = useLocationSearchState<string[]>('qualityProfiles', [], value => value ? value.split(',') : []);
|
|
|
|
const [ruleLang, setLanguage] = useLocationSearchState('lang', 'ANY');
|
2020-07-08 00:30:17 +02:00
|
|
|
|
2020-07-02 23:55:56 +02:00
|
|
|
const [pageNumber, setPageNumber] = useLocationSearchState('page', 1, parseInt);
|
2021-01-26 22:10:28 +01:00
|
|
|
const {setLocationSearch} = useLocationSearch();
|
2020-07-02 23:55:56 +02:00
|
|
|
|
2020-07-02 12:13:51 +02:00
|
|
|
|
2021-01-26 22:10:28 +01:00
|
|
|
const {results, numberOfHits, error, loading} = useSearch(query,
|
2021-06-08 13:55:27 +02:00
|
|
|
ruleType === 'ANY' ? null : ruleType,
|
|
|
|
ruleLang === 'ANY' ? null : ruleLang,
|
2020-07-08 00:30:17 +02:00
|
|
|
ruleTags,
|
2021-06-08 13:55:27 +02:00
|
|
|
qualityProfiles,
|
2020-07-08 00:30:17 +02:00
|
|
|
pageSize, pageNumber);
|
2021-01-26 22:10:28 +01:00
|
|
|
const totalPages = numberOfHits ? Math.ceil(numberOfHits/pageSize) : 0;
|
2020-07-02 12:13:51 +02:00
|
|
|
|
2021-06-08 13:55:27 +02:00
|
|
|
let allRuleTags:string[] = [];
|
|
|
|
let allLangs:string[] = [];
|
|
|
|
let allQualityProfiles = ['Sonar way', 'Sonar way recommended'];
|
|
|
|
const aggregatesDataUrl = `${process.env.PUBLIC_URL}/rules/rule-index-aggregates.json`;
|
|
|
|
const [aggregatesData, aggregatesDataError, aggregatesDataIsLoading] = useFetch<IndexAggregates>(aggregatesDataUrl);
|
|
|
|
|
|
|
|
if (aggregatesData && !aggregatesDataIsLoading && !aggregatesDataError) {
|
|
|
|
allRuleTags = Object.keys(aggregatesData.tags).sort();
|
|
|
|
allLangs = Object.keys(aggregatesData.langs).sort();
|
|
|
|
allQualityProfiles = Object.keys(aggregatesData.qualityProfiles).sort();
|
|
|
|
}
|
|
|
|
|
2021-01-26 22:10:28 +01:00
|
|
|
let resultsDisplay: string|JSX.Element[] = "No rule found...";
|
|
|
|
if (loading) {
|
2020-07-02 12:13:51 +02:00
|
|
|
resultsDisplay = "Searching";
|
|
|
|
}
|
|
|
|
else if (results.length > 0) {
|
2020-07-09 14:26:53 +02:00
|
|
|
resultsDisplay = results.map(result =>
|
|
|
|
<Box className={classes.searchHitBox}>
|
|
|
|
<SearchHit key={result.id} data={result}/>
|
|
|
|
</Box>
|
|
|
|
)
|
2020-07-02 12:13:51 +02:00
|
|
|
}
|
2020-07-02 23:55:56 +02:00
|
|
|
|
2021-06-08 13:55:27 +02:00
|
|
|
const paramSetters: Record<string, SearchParamSetter<any>> = {
|
|
|
|
types: setRuleType,
|
|
|
|
tags: setRuleTags,
|
|
|
|
qualityProfiles: setQualityProfiles,
|
|
|
|
lang:setLanguage,
|
|
|
|
query: setQuery
|
|
|
|
};
|
2021-01-26 22:10:28 +01:00
|
|
|
function handleUpdate(field: string) {
|
|
|
|
return function(event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) {
|
2020-07-09 10:44:35 +02:00
|
|
|
if (pageNumber > 1) {
|
2021-01-26 22:10:28 +01:00
|
|
|
const uriSearch: Record<string, any> = {
|
2021-06-08 13:55:27 +02:00
|
|
|
query: query, types: ruleType, tags: ruleTags, qualityProfiles: qualityProfiles, lang: ruleLang, page: 1
|
2021-01-26 22:10:28 +01:00
|
|
|
};
|
2020-07-09 10:44:35 +02:00
|
|
|
uriSearch[field] = event.target.value;
|
|
|
|
setLocationSearch(uriSearch);
|
|
|
|
} else {
|
|
|
|
paramSetters[field](event.target.value, {push: false});
|
|
|
|
}
|
2020-07-02 23:55:56 +02:00
|
|
|
}
|
|
|
|
}
|
2020-07-08 00:30:17 +02:00
|
|
|
|
2020-07-02 12:13:51 +02:00
|
|
|
return (
|
2020-07-09 14:26:53 +02:00
|
|
|
<div className={classes.root}>
|
|
|
|
<div className={classes.searchBar}>
|
2020-07-02 12:13:51 +02:00
|
|
|
<Container maxWidth="md">
|
2020-07-08 00:30:17 +02:00
|
|
|
<Grid container spacing={3}>
|
|
|
|
<Grid item xs={12}>
|
2020-07-09 14:26:53 +02:00
|
|
|
<Typography variant="h4">Search Rule Specifications</Typography>
|
2020-07-08 00:30:17 +02:00
|
|
|
</Grid>
|
|
|
|
<Grid item xs={12}>
|
|
|
|
<TextField
|
|
|
|
id="title-query"
|
|
|
|
label="Rule Title and Description"
|
|
|
|
placeholder="Search in rule titles and descriptions"
|
|
|
|
fullWidth
|
|
|
|
margin="normal"
|
|
|
|
InputLabelProps={{
|
|
|
|
shrink: true,
|
|
|
|
}}
|
|
|
|
variant="outlined"
|
|
|
|
value={query}
|
2021-06-08 13:55:27 +02:00
|
|
|
onChange={handleUpdate('query')}
|
2021-01-26 22:10:28 +01:00
|
|
|
error={!!error}
|
2020-07-08 00:30:17 +02:00
|
|
|
helperText={error}
|
|
|
|
/>
|
|
|
|
</Grid>
|
|
|
|
<Grid item xs={3}>
|
|
|
|
<TextField
|
|
|
|
select
|
2020-07-02 12:13:51 +02:00
|
|
|
fullWidth
|
|
|
|
margin="normal"
|
2020-07-08 00:30:17 +02:00
|
|
|
variant="outlined"
|
2021-06-08 13:55:27 +02:00
|
|
|
label="Rule type"
|
2020-07-08 00:30:17 +02:00
|
|
|
value={ruleType}
|
2021-06-08 13:55:27 +02:00
|
|
|
onChange={handleUpdate('types')}
|
2020-07-08 00:30:17 +02:00
|
|
|
>
|
2021-06-08 13:55:27 +02:00
|
|
|
<MenuItem key="Any" value="ANY">
|
|
|
|
Any
|
2020-07-08 00:30:17 +02:00
|
|
|
</MenuItem>
|
|
|
|
{Object.keys(allRuleTypes).map((ruleType) => (
|
|
|
|
<MenuItem key={ruleType} value={ruleType}>
|
|
|
|
{allRuleTypes[ruleType]}
|
|
|
|
</MenuItem>
|
|
|
|
))}
|
|
|
|
</TextField>
|
|
|
|
</Grid>
|
2021-06-08 13:55:27 +02:00
|
|
|
<Grid item xs={5}>
|
2020-07-08 00:30:17 +02:00
|
|
|
<TextField
|
|
|
|
select
|
|
|
|
fullWidth
|
|
|
|
SelectProps={{
|
|
|
|
multiple: true,
|
2021-01-26 22:10:28 +01:00
|
|
|
renderValue: (selected: any) => {
|
|
|
|
return selected.join(', ');
|
|
|
|
}
|
2020-07-02 12:13:51 +02:00
|
|
|
}}
|
2020-07-08 00:30:17 +02:00
|
|
|
margin="normal"
|
2020-07-02 12:13:51 +02:00
|
|
|
variant="outlined"
|
2020-07-08 00:30:17 +02:00
|
|
|
label="Rule Tags"
|
|
|
|
value={ruleTags}
|
2021-06-08 13:55:27 +02:00
|
|
|
onChange={handleUpdate('tags')}
|
2020-07-08 00:30:17 +02:00
|
|
|
>
|
2021-06-08 13:55:27 +02:00
|
|
|
{allRuleTags.map((ruleTag) => (
|
|
|
|
<MenuItem key={ruleTag} value={ruleTag}>
|
|
|
|
{ruleTag}
|
|
|
|
</MenuItem>
|
|
|
|
))}
|
|
|
|
</TextField>
|
|
|
|
</Grid>
|
|
|
|
<Grid item xs={4}>
|
|
|
|
<TextField
|
|
|
|
select
|
|
|
|
fullWidth
|
|
|
|
margin="normal"
|
|
|
|
variant="outlined"
|
|
|
|
label="Language"
|
|
|
|
value={ruleLang}
|
|
|
|
onChange={handleUpdate('lang')}
|
|
|
|
>
|
|
|
|
<MenuItem key="Any" value="ANY">
|
|
|
|
Any
|
|
|
|
</MenuItem>
|
|
|
|
{allLangs.map((lang) => (
|
|
|
|
<MenuItem key={lang} value={lang}>
|
|
|
|
{lang}
|
|
|
|
</MenuItem>
|
|
|
|
))}
|
|
|
|
</TextField>
|
|
|
|
</Grid>
|
|
|
|
<Grid item xs={12}>
|
|
|
|
<TextField
|
|
|
|
select
|
|
|
|
fullWidth
|
|
|
|
SelectProps={{
|
|
|
|
multiple: true,
|
|
|
|
renderValue: (selected: any) => {
|
|
|
|
return selected.join(', ');
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
margin="normal"
|
|
|
|
variant="outlined"
|
|
|
|
label="Default Quality Profiles"
|
|
|
|
value={qualityProfiles}
|
|
|
|
onChange={handleUpdate('qualityProfiles')}
|
|
|
|
>
|
|
|
|
{allQualityProfiles.map((qualityProfile) => (
|
|
|
|
<MenuItem key={qualityProfile} value={qualityProfile}>
|
|
|
|
{qualityProfile}
|
2020-07-08 00:30:17 +02:00
|
|
|
</MenuItem>
|
|
|
|
))}
|
|
|
|
</TextField>
|
|
|
|
</Grid>
|
|
|
|
</Grid>
|
2020-07-02 12:13:51 +02:00
|
|
|
</Container>
|
2020-07-09 14:26:53 +02:00
|
|
|
</div>
|
|
|
|
<div className={classes.searchResults}>
|
|
|
|
<Container maxWidth="md">
|
|
|
|
<Grid container spacing={3}>
|
|
|
|
<Grid item xs={12}>
|
2021-05-05 14:50:29 +02:00
|
|
|
<Box className={classes.topRow}>
|
|
|
|
<Box className={classes.resultsCount}>
|
|
|
|
<Typography variant="subtitle1">Number of rules found: {numberOfHits}</Typography>
|
|
|
|
</Box>
|
|
|
|
<Typography variant="subtitle1">
|
|
|
|
<a href={"https://github.com/SonarSource/rspec/pulls?q=is%3Aopen+is%3Apr+%22Create+rule%22+" + query}>Search in unimplemented</a>
|
|
|
|
</Typography>
|
2020-07-09 14:26:53 +02:00
|
|
|
</Box>
|
|
|
|
{resultsDisplay}
|
|
|
|
<Pagination count={totalPages} page={pageNumber} siblingCount={2}
|
|
|
|
onChange={(event, value) => setPageNumber(value)}
|
|
|
|
/>
|
|
|
|
</Grid>
|
2020-07-08 00:30:17 +02:00
|
|
|
</Grid>
|
2020-07-09 14:26:53 +02:00
|
|
|
</Container>
|
|
|
|
</div>
|
2020-07-08 00:30:17 +02:00
|
|
|
</div>
|
2020-07-02 12:13:51 +02:00
|
|
|
)
|
2021-06-08 13:55:27 +02:00
|
|
|
}
|