@@ -73,6 +73,7 @@ def sources = fileTree(dir: "src") + fileTree(dir: "scripts") + fileTree(dir: "c | |||
task licenseCheckWeb(type: com.hierynomus.gradle.license.tasks.LicenseCheck) { | |||
source = sources | |||
exclude 'main/js/helpers/standards.json' | |||
if (official) exclude 'main/js/app/components/GlobalFooterBranding.js' | |||
} | |||
licenseMain.dependsOn licenseCheckWeb |
@@ -49,7 +49,8 @@ import { | |||
ReferencedLanguage, | |||
ReferencedUser, | |||
saveMyIssues, | |||
serializeQuery | |||
serializeQuery, | |||
STANDARDS | |||
} from '../utils'; | |||
import { Component, CurrentUser, Issue, Paging, BranchLike } from '../../../app/types'; | |||
import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication'; | |||
@@ -409,6 +410,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
const facets = requestFacets | |||
? Object.keys(openFacets) | |||
.filter(facet => openFacets[facet]) | |||
.filter(facet => facet !== STANDARDS) | |||
.map(mapFacet) | |||
.join(',') | |||
: undefined; | |||
@@ -640,7 +642,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
this.setState(state => ({ | |||
openFacets: { ...state.openFacets, [property]: !state.openFacets[property] } | |||
})); | |||
if (!this.state.facets[property]) { | |||
if (property !== STANDARDS && !this.state.facets[property]) { | |||
this.fetchFacet(property); | |||
} | |||
}; |
@@ -30,10 +30,18 @@ import ProjectFacet from './ProjectFacet'; | |||
import ResolutionFacet from './ResolutionFacet'; | |||
import RuleFacet from './RuleFacet'; | |||
import SeverityFacet from './SeverityFacet'; | |||
import StandardFacet from './StandardFacet'; | |||
import StatusFacet from './StatusFacet'; | |||
import TagFacet from './TagFacet'; | |||
import TypeFacet from './TypeFacet'; | |||
import { Query, Facet, ReferencedComponent, ReferencedUser, ReferencedLanguage } from '../utils'; | |||
import { | |||
Query, | |||
Facet, | |||
ReferencedComponent, | |||
ReferencedUser, | |||
ReferencedLanguage, | |||
STANDARDS | |||
} from '../utils'; | |||
import { Component } from '../../../app/types'; | |||
export interface Props { | |||
@@ -143,6 +151,21 @@ export default class Sidebar extends React.PureComponent<Props> { | |||
rules={query.rules} | |||
stats={facets.rules} | |||
/> | |||
<StandardFacet | |||
cwe={query.cwe} | |||
cweOpen={!!openFacets.cwe} | |||
cweStats={facets.cwe} | |||
loading={this.props.loading} | |||
onChange={this.props.onFilterChange} | |||
onToggle={this.props.onFacetToggle} | |||
open={!!openFacets[STANDARDS]} | |||
owaspTop10={query.owaspTop10} | |||
owaspTop10Open={!!openFacets.owaspTop10} | |||
owaspTop10Stats={facets.owaspTop10} | |||
sansTop25={query.sansTop25} | |||
sansTop25Open={!!openFacets.sansTop25} | |||
sansTop25Stats={facets.sansTop25} | |||
/> | |||
<TagFacet | |||
component={component} | |||
facetMode={query.facetMode} |
@@ -0,0 +1,308 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { sortBy, without } from 'lodash'; | |||
import { Query, STANDARDS, formatFacetStat } from '../utils'; | |||
import FacetBox from '../../../components/facet/FacetBox'; | |||
import FacetHeader from '../../../components/facet/FacetHeader'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import FacetItemsList from '../../../components/facet/FacetItemsList'; | |||
import FacetItem from '../../../components/facet/FacetItem'; | |||
import Select from '../../../components/controls/Select'; | |||
export interface Props { | |||
cwe: string[]; | |||
cweOpen: boolean; | |||
cweStats: { [x: string]: number } | undefined; | |||
loading?: boolean; | |||
onChange: (changes: Partial<Query>) => void; | |||
onToggle: (property: string) => void; | |||
open: boolean; | |||
owaspTop10: string[]; | |||
owaspTop10Open: boolean; | |||
owaspTop10Stats: { [x: string]: number } | undefined; | |||
sansTop25: string[]; | |||
sansTop25Open: boolean; | |||
sansTop25Stats: { [x: string]: number } | undefined; | |||
} | |||
interface Standards { | |||
owaspTop10: { [x: string]: { title: string } }; | |||
sansTop25: { [x: string]: { title: string } }; | |||
cwe: { [x: string]: { title: string } }; | |||
} | |||
interface State { | |||
standards: Standards; | |||
} | |||
export default class StandardFacet extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
property = STANDARDS; | |||
state: State = { standards: { owaspTop10: {}, sansTop25: {}, cwe: {} } }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
if (this.props.open) { | |||
this.loadStandards(); | |||
} | |||
} | |||
componentDidUpdate(prevProps: Props) { | |||
if (!prevProps.open && this.props.open) { | |||
this.loadStandards(); | |||
} | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
loadStandards = () => { | |||
import('../../../helpers/standards.json') | |||
.then(x => x.default) | |||
.then( | |||
({ owaspTop10, sansTop25, cwe }: Standards) => { | |||
if (this.mounted) { | |||
this.setState({ standards: { owaspTop10, sansTop25, cwe } }); | |||
} | |||
}, | |||
() => {} | |||
); | |||
}; | |||
getValues = () => { | |||
return [ | |||
...this.props.owaspTop10.map(this.renderOwaspTop10Category), | |||
...this.props.sansTop25.map(this.renderSansTop25Category), | |||
...this.props.cwe.map(this.renderCWECategory) | |||
]; | |||
}; | |||
handleHeaderClick = () => { | |||
this.props.onToggle(this.property); | |||
}; | |||
handleOwaspTop10HeaderClick = () => { | |||
this.props.onToggle('owaspTop10'); | |||
}; | |||
handleSansTop25HeaderClick = () => { | |||
this.props.onToggle('sansTop25'); | |||
}; | |||
handleCWEHeaderClick = () => { | |||
this.props.onToggle('cwe'); | |||
}; | |||
handleClear = () => { | |||
this.props.onChange({ [this.property]: [], owaspTop10: [], sansTop25: [], cwe: [] }); | |||
}; | |||
handleItemClick = ( | |||
prop: 'owaspTop10' | 'sansTop25' | 'cwe', | |||
itemValue: string, | |||
multiple: boolean | |||
) => { | |||
const items = this.props[prop]; | |||
if (multiple) { | |||
const newValue = sortBy( | |||
items.includes(itemValue) ? without(items, itemValue) : [...items, itemValue] | |||
); | |||
this.props.onChange({ [prop]: newValue }); | |||
} else { | |||
this.props.onChange({ | |||
[prop]: items.includes(itemValue) && items.length < 2 ? [] : [itemValue] | |||
}); | |||
} | |||
}; | |||
handleOwaspTop10ItemClick = (itemValue: string, multiple: boolean) => { | |||
this.handleItemClick('owaspTop10', itemValue, multiple); | |||
}; | |||
handleCWEItemClick = (itemValue: string, multiple: boolean) => { | |||
this.handleItemClick('cwe', itemValue, multiple); | |||
}; | |||
handleSansTop25ItemClick = (itemValue: string, multiple: boolean) => { | |||
this.handleItemClick('sansTop25', itemValue, multiple); | |||
}; | |||
handleCWESelect = ({ value }: { value: string }) => { | |||
this.handleItemClick('cwe', value, true); | |||
}; | |||
renderOwaspTop10Category = (category: string) => { | |||
const record = this.state.standards.owaspTop10[category]; | |||
if (!record) { | |||
return category.toUpperCase(); | |||
} else if (category === 'unknown') { | |||
return record.title; | |||
} else { | |||
return `${category.toUpperCase()} - ${record.title}`; | |||
} | |||
}; | |||
renderCWECategory = (category: string) => { | |||
const record = this.state.standards.cwe[category]; | |||
if (!record) { | |||
return `CWE-${category}`; | |||
} else if (category === 'unknown') { | |||
return record.title; | |||
} else { | |||
return `CWE-${category} - ${record.title}`; | |||
} | |||
}; | |||
renderSansTop25Category = (category: string) => { | |||
const record = this.state.standards.sansTop25[category]; | |||
return record ? record.title : category; | |||
}; | |||
renderList = ( | |||
statsProp: 'owaspTop10Stats' | 'cweStats' | 'sansTop25Stats', | |||
valuesProp: 'owaspTop10' | 'cwe' | 'sansTop25', | |||
renderName: (category: string) => string, | |||
onClick: (x: string, multiple?: boolean) => void | |||
) => { | |||
const stats = this.props[statsProp]; | |||
const values = this.props[valuesProp]; | |||
if (!stats) { | |||
return null; | |||
} | |||
const categories = sortBy(Object.keys(stats), key => -stats[key]); | |||
const getStat = (category: string) => { | |||
return stats ? stats[category] : undefined; | |||
}; | |||
return ( | |||
<FacetItemsList> | |||
{categories.map(category => ( | |||
<FacetItem | |||
active={values.includes(category)} | |||
key={category} | |||
loading={this.props.loading} | |||
name={renderName(category)} | |||
onClick={onClick} | |||
stat={formatFacetStat(getStat(category), '')} | |||
tooltip={values.length === 1 && !values.includes(category)} | |||
value={category} | |||
/> | |||
))} | |||
</FacetItemsList> | |||
); | |||
}; | |||
renderOwaspTop10List() { | |||
return this.renderList( | |||
'owaspTop10Stats', | |||
'owaspTop10', | |||
this.renderOwaspTop10Category, | |||
this.handleOwaspTop10ItemClick | |||
); | |||
} | |||
renderCWEList() { | |||
return this.renderList('cweStats', 'cwe', this.renderCWECategory, this.handleCWEItemClick); | |||
} | |||
renderCWESearch() { | |||
const options = Object.keys(this.state.standards.cwe).map(cwe => ({ | |||
label: this.renderCWECategory(cwe), | |||
value: cwe | |||
})); | |||
return ( | |||
<div className="search-navigator-facet-footer"> | |||
<Select | |||
className="input-super-large" | |||
clearable={false} | |||
noResultsText={translate('select2.noMatches')} | |||
onChange={this.handleCWESelect} | |||
options={options} | |||
placeholder={translate('search.search_for_cwe')} | |||
searchable={true} | |||
/> | |||
</div> | |||
); | |||
} | |||
renderSansTop25List() { | |||
return this.renderList( | |||
'sansTop25Stats', | |||
'sansTop25', | |||
this.renderSansTop25Category, | |||
this.handleSansTop25ItemClick | |||
); | |||
} | |||
renderSubFacets() { | |||
return ( | |||
<> | |||
<FacetBox className="is-inner" property="owaspTop10"> | |||
<FacetHeader | |||
name={translate('issues.facet.owaspTop10')} | |||
onClick={this.handleOwaspTop10HeaderClick} | |||
open={this.props.owaspTop10Open} | |||
values={this.props.owaspTop10.map(this.renderOwaspTop10Category)} | |||
/> | |||
{this.props.owaspTop10Open && this.renderOwaspTop10List()} | |||
</FacetBox> | |||
<FacetBox className="is-inner" property="sansTop25"> | |||
<FacetHeader | |||
name={translate('issues.facet.sansTop25')} | |||
onClick={this.handleSansTop25HeaderClick} | |||
open={this.props.sansTop25Open} | |||
values={this.props.sansTop25.map(this.renderSansTop25Category)} | |||
/> | |||
{this.props.sansTop25Open && this.renderSansTop25List()} | |||
</FacetBox> | |||
<FacetBox className="is-inner" property="cwe"> | |||
<FacetHeader | |||
name={translate('issues.facet.cwe')} | |||
onClick={this.handleCWEHeaderClick} | |||
open={this.props.cweOpen} | |||
values={this.props.cwe.map(this.renderCWECategory)} | |||
/> | |||
{this.props.cweOpen && this.renderCWEList()} | |||
{this.props.cweOpen && this.renderCWESearch()} | |||
</FacetBox> | |||
</> | |||
); | |||
} | |||
render() { | |||
return ( | |||
<FacetBox property={this.property}> | |||
<FacetHeader | |||
name={translate('issues.facet', this.property)} | |||
onClear={this.handleClear} | |||
onClick={this.handleHeaderClick} | |||
open={this.props.open} | |||
values={this.getValues()} | |||
/> | |||
{this.props.open && this.renderSubFacets()} | |||
</FacetBox> | |||
); | |||
} | |||
} |
@@ -0,0 +1,170 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import StandardFacet, { Props } from '../StandardFacet'; | |||
import { click } from '../../../../helpers/testUtils'; | |||
it('should render closed', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
}); | |||
it('should toggle standards facet', () => { | |||
const onToggle = jest.fn(); | |||
const wrapper = shallowRender({ onToggle }); | |||
click(wrapper.children('FacetHeader')); | |||
expect(onToggle).toBeCalledWith('standards'); | |||
}); | |||
it('should clear standards facet', () => { | |||
const onChange = jest.fn(); | |||
const wrapper = shallowRender({ onChange }); | |||
wrapper.children('FacetHeader').prop<Function>('onClear')(); | |||
expect(onChange).toBeCalledWith({ cwe: [], owaspTop10: [], sansTop25: [], standards: [] }); | |||
}); | |||
it('should render sub-facets', () => { | |||
expect( | |||
shallowRender({ | |||
cwe: ['42'], | |||
cweOpen: true, | |||
cweStats: { 42: 5, 173: 3 }, | |||
open: true, | |||
owaspTop10: ['a3'], | |||
owaspTop10Open: true, | |||
owaspTop10Stats: { a1: 15, a3: 5 }, | |||
sansTop25: ['risky-resource'], | |||
sansTop25Open: true, | |||
sansTop25Stats: { foo: 12, 'risky-resource': 10 } | |||
}) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should select items', () => { | |||
const onChange = jest.fn(); | |||
const wrapper = shallowRender({ | |||
cwe: ['42'], | |||
cweOpen: true, | |||
cweStats: { 42: 5, 173: 3 }, | |||
onChange, | |||
open: true, | |||
owaspTop10: ['a3'], | |||
owaspTop10Open: true, | |||
owaspTop10Stats: { a1: 15, a3: 5 }, | |||
sansTop25: ['risky-resource'], | |||
sansTop25Open: true, | |||
sansTop25Stats: { foo: 12, 'risky-resource': 10 } | |||
}); | |||
selectAndCheck('owaspTop10', 'a1'); | |||
selectAndCheck('owaspTop10', 'a1', true, ['a1', 'a3']); | |||
selectAndCheck('sansTop25', 'foo'); | |||
selectAndCheck('cwe', '173'); | |||
function selectAndCheck(facet: string, value: string, multiple = false, expectedValue = [value]) { | |||
wrapper | |||
.find(`FacetBox[property="${facet}"]`) | |||
.find(`FacetItem[value="${value}"]`) | |||
.prop<Function>('onClick')(value, multiple); | |||
expect(onChange).lastCalledWith({ [facet]: expectedValue }); | |||
} | |||
}); | |||
it('should toggle sub-facets', () => { | |||
const onToggle = jest.fn(); | |||
const wrapper = shallowRender({ onToggle, open: true }); | |||
click(wrapper.find('FacetBox[property="owaspTop10"]').children('FacetHeader')); | |||
expect(onToggle).lastCalledWith('owaspTop10'); | |||
click(wrapper.find('FacetBox[property="cwe"]').children('FacetHeader')); | |||
expect(onToggle).lastCalledWith('cwe'); | |||
click(wrapper.find('FacetBox[property="sansTop25"]').children('FacetHeader')); | |||
expect(onToggle).lastCalledWith('sansTop25'); | |||
}); | |||
it('should display correct selection', () => { | |||
const wrapper = shallowRender({ | |||
open: true, | |||
owaspTop10: ['a1', 'a3', 'unknown'], | |||
sansTop25: ['risky-resource', 'foo'], | |||
cwe: ['42', '1111', 'unknown'] | |||
}); | |||
checkValues('standards', [ | |||
'A1 - a1 title', | |||
'A3', | |||
'Not OWAPS', | |||
'Risky Resource Management', | |||
'foo', | |||
'CWE-42 - cwe-42 title', | |||
'CWE-1111', | |||
'Unknown CWE' | |||
]); | |||
checkValues('owaspTop10', ['A1 - a1 title', 'A3', 'Not OWAPS']); | |||
checkValues('cwe', ['CWE-42 - cwe-42 title', 'CWE-1111', 'Unknown CWE']); | |||
checkValues('sansTop25', ['Risky Resource Management', 'foo']); | |||
function checkValues(property: string, values: string[]) { | |||
expect( | |||
wrapper | |||
.find(`FacetBox[property="${property}"]`) | |||
.children('FacetHeader') | |||
.prop('values') | |||
).toEqual(values); | |||
} | |||
}); | |||
it('should search CWE', () => { | |||
const onChange = jest.fn(); | |||
const wrapper = shallowRender({ onChange, open: true, cwe: ['42'], cweOpen: true }); | |||
wrapper | |||
.find('FacetBox[property="cwe"]') | |||
.find('Select') | |||
.prop<Function>('onChange')({ value: '111' }); | |||
expect(onChange).toBeCalledWith({ cwe: ['111', '42'] }); | |||
}); | |||
function shallowRender(props: Partial<Props> = {}) { | |||
const wrapper = shallow( | |||
<StandardFacet | |||
cwe={[]} | |||
cweOpen={false} | |||
cweStats={{}} | |||
onChange={jest.fn()} | |||
onToggle={jest.fn()} | |||
open={false} | |||
owaspTop10={[]} | |||
owaspTop10Open={false} | |||
owaspTop10Stats={{}} | |||
sansTop25={[]} | |||
sansTop25Open={false} | |||
sansTop25Stats={{}} | |||
{...props} | |||
/>, | |||
// disable loading of standards.json | |||
{ disableLifecycleMethods: true } | |||
); | |||
wrapper.setState({ | |||
standards: { | |||
owaspTop10: { a1: { title: 'a1 title' }, unknown: { title: 'Not OWAPS' } }, | |||
sansTop25: { 'risky-resource': { title: 'Risky Resource Management' } }, | |||
cwe: { 42: { title: 'cwe-42 title' }, unknown: { title: 'Unknown CWE' } } | |||
} | |||
}); | |||
return wrapper; | |||
} |
@@ -10,6 +10,7 @@ Array [ | |||
"CreationDateFacet", | |||
"LanguageFacet", | |||
"RuleFacet", | |||
"StandardFacet", | |||
"TagFacet", | |||
"ProjectFacet", | |||
"ModuleFacet", | |||
@@ -29,6 +30,7 @@ Array [ | |||
"CreationDateFacet", | |||
"LanguageFacet", | |||
"RuleFacet", | |||
"StandardFacet", | |||
"TagFacet", | |||
"FileFacet", | |||
"AssigneeFacet", | |||
@@ -46,6 +48,7 @@ Array [ | |||
"CreationDateFacet", | |||
"LanguageFacet", | |||
"RuleFacet", | |||
"StandardFacet", | |||
"TagFacet", | |||
"ProjectFacet", | |||
"AssigneeFacet", | |||
@@ -63,6 +66,7 @@ Array [ | |||
"CreationDateFacet", | |||
"LanguageFacet", | |||
"RuleFacet", | |||
"StandardFacet", | |||
"TagFacet", | |||
"ModuleFacet", | |||
"DirectoryFacet", | |||
@@ -82,6 +86,7 @@ Array [ | |||
"CreationDateFacet", | |||
"LanguageFacet", | |||
"RuleFacet", | |||
"StandardFacet", | |||
"TagFacet", | |||
"ModuleFacet", | |||
"DirectoryFacet", | |||
@@ -101,6 +106,7 @@ Array [ | |||
"CreationDateFacet", | |||
"LanguageFacet", | |||
"RuleFacet", | |||
"StandardFacet", | |||
"TagFacet", | |||
"ProjectFacet", | |||
"AuthorFacet", |
@@ -0,0 +1,184 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render closed 1`] = ` | |||
<FacetBox | |||
property="standards" | |||
> | |||
<FacetHeader | |||
name="issues.facet.standards" | |||
onClear={[Function]} | |||
onClick={[Function]} | |||
open={false} | |||
values={Array []} | |||
/> | |||
</FacetBox> | |||
`; | |||
exports[`should render sub-facets 1`] = ` | |||
<FacetBox | |||
property="standards" | |||
> | |||
<FacetHeader | |||
name="issues.facet.standards" | |||
onClear={[Function]} | |||
onClick={[Function]} | |||
open={true} | |||
values={ | |||
Array [ | |||
"A3", | |||
"Risky Resource Management", | |||
"CWE-42 - cwe-42 title", | |||
] | |||
} | |||
/> | |||
<React.Fragment> | |||
<FacetBox | |||
className="is-inner" | |||
property="owaspTop10" | |||
> | |||
<FacetHeader | |||
name="issues.facet.owaspTop10" | |||
onClick={[Function]} | |||
open={true} | |||
values={ | |||
Array [ | |||
"A3", | |||
] | |||
} | |||
/> | |||
<FacetItemsList> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="a1" | |||
loading={false} | |||
name="A1 - a1 title" | |||
onClick={[Function]} | |||
stat="15" | |||
tooltip={true} | |||
value="a1" | |||
/> | |||
<FacetItem | |||
active={true} | |||
disabled={false} | |||
halfWidth={false} | |||
key="a3" | |||
loading={false} | |||
name="A3" | |||
onClick={[Function]} | |||
stat="5" | |||
tooltip={false} | |||
value="a3" | |||
/> | |||
</FacetItemsList> | |||
</FacetBox> | |||
<FacetBox | |||
className="is-inner" | |||
property="sansTop25" | |||
> | |||
<FacetHeader | |||
name="issues.facet.sansTop25" | |||
onClick={[Function]} | |||
open={true} | |||
values={ | |||
Array [ | |||
"Risky Resource Management", | |||
] | |||
} | |||
/> | |||
<FacetItemsList> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="foo" | |||
loading={false} | |||
name="foo" | |||
onClick={[Function]} | |||
stat="12" | |||
tooltip={true} | |||
value="foo" | |||
/> | |||
<FacetItem | |||
active={true} | |||
disabled={false} | |||
halfWidth={false} | |||
key="risky-resource" | |||
loading={false} | |||
name="Risky Resource Management" | |||
onClick={[Function]} | |||
stat="10" | |||
tooltip={false} | |||
value="risky-resource" | |||
/> | |||
</FacetItemsList> | |||
</FacetBox> | |||
<FacetBox | |||
className="is-inner" | |||
property="cwe" | |||
> | |||
<FacetHeader | |||
name="issues.facet.cwe" | |||
onClick={[Function]} | |||
open={true} | |||
values={ | |||
Array [ | |||
"CWE-42 - cwe-42 title", | |||
] | |||
} | |||
/> | |||
<FacetItemsList> | |||
<FacetItem | |||
active={true} | |||
disabled={false} | |||
halfWidth={false} | |||
key="42" | |||
loading={false} | |||
name="CWE-42 - cwe-42 title" | |||
onClick={[Function]} | |||
stat="5" | |||
tooltip={false} | |||
value="42" | |||
/> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="173" | |||
loading={false} | |||
name="CWE-173" | |||
onClick={[Function]} | |||
stat="3" | |||
tooltip={true} | |||
value="173" | |||
/> | |||
</FacetItemsList> | |||
<div | |||
className="search-navigator-facet-footer" | |||
> | |||
<Select | |||
className="input-super-large" | |||
clearable={false} | |||
noResultsText="select2.noMatches" | |||
onChange={[Function]} | |||
options={ | |||
Array [ | |||
Object { | |||
"label": "CWE-42 - cwe-42 title", | |||
"value": "42", | |||
}, | |||
Object { | |||
"label": "Unknown CWE", | |||
"value": "unknown", | |||
}, | |||
] | |||
} | |||
placeholder="search.search_for_cwe" | |||
searchable={true} | |||
/> | |||
</div> | |||
</FacetBox> | |||
</React.Fragment> | |||
</FacetBox> | |||
`; |
@@ -44,24 +44,29 @@ export interface Query { | |||
createdAt: string; | |||
createdBefore: Date | undefined; | |||
createdInLast: string; | |||
cwe: string[]; | |||
directories: string[]; | |||
facetMode: string; | |||
files: string[]; | |||
issues: string[]; | |||
languages: string[]; | |||
modules: string[]; | |||
owaspTop10: string[]; | |||
projects: string[]; | |||
resolved: boolean; | |||
resolutions: string[]; | |||
resolved: boolean; | |||
rules: string[]; | |||
sort: string; | |||
sansTop25: string[]; | |||
severities: string[]; | |||
sinceLeakPeriod: boolean; | |||
sort: string; | |||
statuses: string[]; | |||
tags: string[]; | |||
types: string[]; | |||
} | |||
export const STANDARDS = 'standards'; | |||
// allow sorting by CREATION_DATE only | |||
const parseAsSort = (sort: string) => (sort === 'CREATION_DATE' ? 'CREATION_DATE' : ''); | |||
const ISSUES_DEFAULT = 'sonarqube.issues.default'; | |||
@@ -75,19 +80,22 @@ export function parseQuery(query: RawQuery): Query { | |||
createdAt: parseAsString(query.createdAt), | |||
createdBefore: parseAsDate(query.createdBefore), | |||
createdInLast: parseAsString(query.createdInLast), | |||
cwe: parseAsArray(query.cwe, parseAsString), | |||
directories: parseAsArray(query.directories, parseAsString), | |||
facetMode: parseAsFacetMode(query.facetMode), | |||
files: parseAsArray(query.fileUuids, parseAsString), | |||
issues: parseAsArray(query.issues, parseAsString), | |||
languages: parseAsArray(query.languages, parseAsString), | |||
modules: parseAsArray(query.moduleUuids, parseAsString), | |||
owaspTop10: parseAsArray(query.owaspTop10, parseAsString), | |||
projects: parseAsArray(query.projectUuids, parseAsString), | |||
resolved: parseAsBoolean(query.resolved), | |||
resolutions: parseAsArray(query.resolutions, parseAsString), | |||
resolved: parseAsBoolean(query.resolved), | |||
rules: parseAsArray(query.rules, parseAsString), | |||
sort: parseAsSort(query.s), | |||
sansTop25: parseAsArray(query.sansTop25, parseAsString), | |||
severities: parseAsArray(query.severities, parseAsString), | |||
sinceLeakPeriod: parseAsBoolean(query.sinceLeakPeriod, false), | |||
sort: parseAsSort(query.s), | |||
statuses: parseAsArray(query.statuses, parseAsString), | |||
tags: parseAsArray(query.tags, parseAsString), | |||
types: parseAsArray(query.types, parseAsString) | |||
@@ -109,20 +117,23 @@ export function serializeQuery(query: Query): RawQuery { | |||
createdAt: serializeString(query.createdAt), | |||
createdBefore: serializeDateShort(query.createdBefore), | |||
createdInLast: serializeString(query.createdInLast), | |||
cwe: serializeStringArray(query.cwe), | |||
directories: serializeStringArray(query.directories), | |||
facetMode: query.facetMode === 'effort' ? serializeString(query.facetMode) : undefined, | |||
fileUuids: serializeStringArray(query.files), | |||
issues: serializeStringArray(query.issues), | |||
languages: serializeStringArray(query.languages), | |||
moduleUuids: serializeStringArray(query.modules), | |||
owaspTop10: serializeStringArray(query.owaspTop10), | |||
projectUuids: serializeStringArray(query.projects), | |||
resolved: query.resolved ? undefined : 'false', | |||
resolutions: serializeStringArray(query.resolutions), | |||
resolved: query.resolved ? undefined : 'false', | |||
rules: serializeStringArray(query.rules), | |||
s: serializeString(query.sort), | |||
sansTop25: serializeStringArray(query.sansTop25), | |||
severities: serializeStringArray(query.severities), | |||
sinceLeakPeriod: query.sinceLeakPeriod ? 'true' : undefined, | |||
statuses: serializeStringArray(query.statuses), | |||
rules: serializeStringArray(query.rules), | |||
tags: serializeStringArray(query.tags), | |||
types: serializeStringArray(query.types) | |||
}; |
@@ -43,6 +43,17 @@ | |||
border: 1px solid var(--leakBorderColor); | |||
} | |||
.search-navigator-facet-box.is-inner { | |||
margin-left: 8px; | |||
padding-left: 12px; | |||
border-left: 1px solid var(--barBorderColor); | |||
} | |||
.search-navigator-facet-box.is-inner .search-navigator-facet-header { | |||
padding-top: 6px; | |||
padding-bottom: 6px; | |||
} | |||
.leak-facet-box:not(.hidden) + .leak-facet-box { | |||
border-top: none; | |||
} |
@@ -708,7 +708,10 @@ issues.facet.issues=Issue Key | |||
issues.facet.mode=Display Mode | |||
issues.facet.mode.count=Issues | |||
issues.facet.mode.effort=Effort | |||
issues.facet.standards=Standard | |||
issues.facet.owaspTop10=OWASP Top 10 | |||
issues.facet.sansTop25=SANS Top 25 | |||
issues.facet.cwe=CWE | |||
#------------------------------------------------------------------------------ | |||
# | |||
@@ -905,6 +908,7 @@ search.search_by_name_or_key=Search by name or key... | |||
search.search_for_tags=Search for tags... | |||
search.search_for_rules=Search for rules... | |||
search.search_for_languages=Search for languages... | |||
search.search_for_cwe=Search for CWEs... | |||
#------------------------------------------------------------------------------ |