@@ -19,16 +19,20 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import { Helmet } from 'react-helmet-async'; | |||
import { Link } from 'react-router'; | |||
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; | |||
import A11ySkipTarget from '../../app/components/a11y/A11ySkipTarget'; | |||
import Suggestions from '../../app/components/embed-docs-modal/Suggestions'; | |||
import ScreenPositionHelper from '../../components/common/ScreenPositionHelper'; | |||
import { isBranch } from '../../helpers/branch-like'; | |||
import { BranchLike } from '../../types/branch-like'; | |||
import { HotspotFilters, HotspotUpdate, RawHotspot } from '../../types/security-hotspots'; | |||
import { | |||
HotspotFilters, | |||
HotspotStatusFilter, | |||
HotspotUpdate, | |||
RawHotspot | |||
} from '../../types/security-hotspots'; | |||
import EmptyHotspotsPage from './components/EmptyHotspotsPage'; | |||
import FilterBar from './components/FilterBar'; | |||
import HotspotList from './components/HotspotList'; | |||
import HotspotViewer from './components/HotspotViewer'; | |||
@@ -51,37 +55,6 @@ export interface SecurityHotspotsAppRendererProps { | |||
securityCategories: T.StandardSecurityCategories; | |||
} | |||
function renderNoHotspots(filtered: boolean) { | |||
return ( | |||
<div className="display-flex-column display-flex-center huge-spacer-top"> | |||
<img | |||
alt={translate('hotspots.page')} | |||
className="huge-spacer-top" | |||
height={100} | |||
src={`${getBaseUrl()}/images/${filtered ? 'filter-large' : 'hotspot-large'}.svg`} | |||
/> | |||
<h1 className="huge-spacer-top"> | |||
{filtered | |||
? translate('hotspots.no_hotspots_for_filters.title') | |||
: translate('hotspots.no_hotspots.title')} | |||
</h1> | |||
<div className="abs-width-400 text-center big-spacer-top"> | |||
{filtered | |||
? translate('hotspots.no_hotspots_for_filters.description') | |||
: translate('hotspots.no_hotspots.description')} | |||
</div> | |||
{!filtered && ( | |||
<Link | |||
className="big-spacer-top" | |||
target="_blank" | |||
to={{ pathname: '/documentation/user-guide/security-hotspots/' }}> | |||
{translate('hotspots.learn_more')} | |||
</Link> | |||
)} | |||
</div> | |||
); | |||
} | |||
export default function SecurityHotspotsAppRenderer(props: SecurityHotspotsAppRendererProps) { | |||
const { | |||
branchLike, | |||
@@ -117,9 +90,14 @@ export default function SecurityHotspotsAppRenderer(props: SecurityHotspotsAppRe | |||
) : ( | |||
<> | |||
{hotspots.length === 0 ? ( | |||
renderNoHotspots( | |||
filters.assignedToMe || (filters.sinceLeakPeriod && isBranch(branchLike)) | |||
) | |||
<EmptyHotspotsPage | |||
filtered={ | |||
filters.assignedToMe || | |||
(isBranch(branchLike) && filters.sinceLeakPeriod) || | |||
filters.status !== HotspotStatusFilter.TO_REVIEW | |||
} | |||
isStaticListOfHotspots={isStaticListOfHotspots} | |||
/> | |||
) : ( | |||
<div className="layout-page"> | |||
<div className="sidebar"> |
@@ -209,39 +209,10 @@ exports[`should render correctly: no hotspots 1`] = ` | |||
<A11ySkipTarget | |||
anchor="security_hotspots_main" | |||
/> | |||
<div | |||
className="display-flex-column display-flex-center huge-spacer-top" | |||
> | |||
<img | |||
alt="hotspots.page" | |||
className="huge-spacer-top" | |||
height={100} | |||
src="/images/hotspot-large.svg" | |||
/> | |||
<h1 | |||
className="huge-spacer-top" | |||
> | |||
hotspots.no_hotspots.title | |||
</h1> | |||
<div | |||
className="abs-width-400 text-center big-spacer-top" | |||
> | |||
hotspots.no_hotspots.description | |||
</div> | |||
<Link | |||
className="big-spacer-top" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
target="_blank" | |||
to={ | |||
Object { | |||
"pathname": "/documentation/user-guide/security-hotspots/", | |||
} | |||
} | |||
> | |||
hotspots.learn_more | |||
</Link> | |||
</div> | |||
<EmptyHotspotsPage | |||
filtered={false} | |||
isStaticListOfHotspots={true} | |||
/> | |||
</div> | |||
</div> | |||
`; | |||
@@ -267,26 +238,10 @@ exports[`should render correctly: no hotspots with filters 1`] = ` | |||
<A11ySkipTarget | |||
anchor="security_hotspots_main" | |||
/> | |||
<div | |||
className="display-flex-column display-flex-center huge-spacer-top" | |||
> | |||
<img | |||
alt="hotspots.page" | |||
className="huge-spacer-top" | |||
height={100} | |||
src="/images/filter-large.svg" | |||
/> | |||
<h1 | |||
className="huge-spacer-top" | |||
> | |||
hotspots.no_hotspots_for_filters.title | |||
</h1> | |||
<div | |||
className="abs-width-400 text-center big-spacer-top" | |||
> | |||
hotspots.no_hotspots_for_filters.description | |||
</div> | |||
</div> | |||
<EmptyHotspotsPage | |||
filtered={true} | |||
isStaticListOfHotspots={true} | |||
/> | |||
</div> | |||
</div> | |||
`; |
@@ -0,0 +1,64 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 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 { Link } from 'react-router'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; | |||
export interface EmptyHotspotsPageProps { | |||
filtered: boolean; | |||
isStaticListOfHotspots: boolean; | |||
} | |||
export default function EmptyHotspotsPage(props: EmptyHotspotsPageProps) { | |||
const { filtered, isStaticListOfHotspots } = props; | |||
let translationRoot; | |||
if (isStaticListOfHotspots) { | |||
translationRoot = 'no_hotspots_for_keys'; | |||
} else if (filtered) { | |||
translationRoot = 'no_hotspots_for_filters'; | |||
} else { | |||
translationRoot = 'no_hotspots'; | |||
} | |||
return ( | |||
<div className="display-flex-column display-flex-center huge-spacer-top"> | |||
<img | |||
alt={translate('hotspots.page')} | |||
className="huge-spacer-top" | |||
height={100} | |||
src={`${getBaseUrl()}/images/${filtered ? 'filter-large' : 'hotspot-large'}.svg`} | |||
/> | |||
<h1 className="huge-spacer-top">{translate(`hotspots.${translationRoot}.title`)}</h1> | |||
<div className="abs-width-400 text-center big-spacer-top"> | |||
{translate(`hotspots.${translationRoot}.description`)} | |||
</div> | |||
{!(filtered || isStaticListOfHotspots) && ( | |||
<Link | |||
className="big-spacer-top" | |||
target="_blank" | |||
to={{ pathname: '/documentation/user-guide/security-hotspots/' }}> | |||
{translate('hotspots.learn_more')} | |||
</Link> | |||
)} | |||
</div> | |||
); | |||
} |
@@ -0,0 +1,32 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import EmptyHotspotsPage, { EmptyHotspotsPageProps } from '../EmptyHotspotsPage'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
expect(shallowRender({ filtered: true })).toMatchSnapshot('filtered'); | |||
expect(shallowRender({ isStaticListOfHotspots: true })).toMatchSnapshot('keys'); | |||
}); | |||
function shallowRender(props: Partial<EmptyHotspotsPageProps> = {}) { | |||
return shallow(<EmptyHotspotsPage filtered={false} isStaticListOfHotspots={false} {...props} />); | |||
} |
@@ -0,0 +1,83 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<div | |||
className="display-flex-column display-flex-center huge-spacer-top" | |||
> | |||
<img | |||
alt="hotspots.page" | |||
className="huge-spacer-top" | |||
height={100} | |||
src="/images/hotspot-large.svg" | |||
/> | |||
<h1 | |||
className="huge-spacer-top" | |||
> | |||
hotspots.no_hotspots.title | |||
</h1> | |||
<div | |||
className="abs-width-400 text-center big-spacer-top" | |||
> | |||
hotspots.no_hotspots.description | |||
</div> | |||
<Link | |||
className="big-spacer-top" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
target="_blank" | |||
to={ | |||
Object { | |||
"pathname": "/documentation/user-guide/security-hotspots/", | |||
} | |||
} | |||
> | |||
hotspots.learn_more | |||
</Link> | |||
</div> | |||
`; | |||
exports[`should render correctly: filtered 1`] = ` | |||
<div | |||
className="display-flex-column display-flex-center huge-spacer-top" | |||
> | |||
<img | |||
alt="hotspots.page" | |||
className="huge-spacer-top" | |||
height={100} | |||
src="/images/filter-large.svg" | |||
/> | |||
<h1 | |||
className="huge-spacer-top" | |||
> | |||
hotspots.no_hotspots_for_filters.title | |||
</h1> | |||
<div | |||
className="abs-width-400 text-center big-spacer-top" | |||
> | |||
hotspots.no_hotspots_for_filters.description | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render correctly: keys 1`] = ` | |||
<div | |||
className="display-flex-column display-flex-center huge-spacer-top" | |||
> | |||
<img | |||
alt="hotspots.page" | |||
className="huge-spacer-top" | |||
height={100} | |||
src="/images/hotspot-large.svg" | |||
/> | |||
<h1 | |||
className="huge-spacer-top" | |||
> | |||
hotspots.no_hotspots_for_keys.title | |||
</h1> | |||
<div | |||
className="abs-width-400 text-center big-spacer-top" | |||
> | |||
hotspots.no_hotspots_for_keys.description | |||
</div> | |||
</div> | |||
`; |
@@ -649,6 +649,8 @@ hotspots.no_hotspots.title=There are no Security Hotspots to review. | |||
hotspots.no_hotspots.description=Next time you analyse a piece of code that contains a potential security risk, it will show up here. | |||
hotspots.no_hotspots_for_filters.title=We couldn't find any results matching the selected criteria. | |||
hotspots.no_hotspots_for_filters.description=Try changing the filters to get some results. | |||
hotspots.no_hotspots_for_keys.title=The requested hotspots no longer exist. | |||
hotspots.no_hotspots_for_keys.description=They have been closed because the code involved has been changed or removed. | |||
hotspots.learn_more=Learn more about Security Hotspots | |||
hotspots.list_title={0} Security Hotspots | |||
hotspots.list_title.TO_REVIEW={0} Security Hotspots to review |