@@ -154,7 +154,7 @@ th.hide-overflow { | |||
} | |||
.padded { | |||
padding: var(--gridSize); | |||
padding: var(--gridSize) !important; | |||
} | |||
.big-padded { |
@@ -164,23 +164,23 @@ blockquote cite { | |||
small, | |||
.small { | |||
font-size: var(--smallFontSize); | |||
font-size: var(--smallFontSize) !important; | |||
} | |||
.medium { | |||
font-size: var(--mediumFontSize); | |||
font-size: var(--mediumFontSize) !important; | |||
} | |||
.big { | |||
font-size: var(--bigFontSize); | |||
font-size: var(--bigFontSize) !important; | |||
} | |||
.huge { | |||
font-size: var(--hugeFontSize); | |||
font-size: var(--hugeFontSize) !important; | |||
} | |||
.gigantic { | |||
font-size: var(--giganticFontSize); | |||
font-size: var(--giganticFontSize) !important; | |||
} | |||
.zero-font-size { |
@@ -347,6 +347,10 @@ export class SecurityHotspotsApp extends React.PureComponent<Props, State> { | |||
); | |||
}; | |||
handleChangeStatusFilter = (status: HotspotStatusFilter) => { | |||
this.handleChangeFilters({ status }); | |||
}; | |||
handleHotspotClick = (selectedHotspot: RawHotspot) => this.setState({ selectedHotspot }); | |||
handleHotspotUpdate = (hotspotKey: string) => { | |||
@@ -452,6 +456,7 @@ export class SecurityHotspotsApp extends React.PureComponent<Props, State> { | |||
onHotspotClick={this.handleHotspotClick} | |||
onLoadMore={this.handleLoadMore} | |||
onShowAllHotspots={this.handleShowAllHotspots} | |||
onSwitchStatusFilter={this.handleChangeStatusFilter} | |||
onUpdateHotspot={this.handleHotspotUpdate} | |||
securityCategories={standards[SecurityStandard.SONARSOURCE]} | |||
selectedHotspot={selectedHotspot} |
@@ -57,6 +57,7 @@ export interface SecurityHotspotsAppRendererProps { | |||
onHotspotClick: (hotspot: RawHotspot) => void; | |||
onLoadMore: () => void; | |||
onShowAllHotspots: () => void; | |||
onSwitchStatusFilter: (option: HotspotStatusFilter) => void; | |||
onUpdateHotspot: (hotspotKey: string) => Promise<void>; | |||
selectedHotspot: RawHotspot | undefined; | |||
securityCategories: T.StandardSecurityCategories; | |||
@@ -172,6 +173,8 @@ export default function SecurityHotspotsAppRenderer(props: SecurityHotspotsAppRe | |||
branchLike={branchLike} | |||
component={component} | |||
hotspotKey={selectedHotspot.key} | |||
hotspotsReviewedMeasure={hotspotsReviewedMeasure} | |||
onSwitchStatusFilter={props.onSwitchStatusFilter} | |||
onUpdateHotspot={props.onUpdateHotspot} | |||
securityCategories={securityCategories} | |||
/> |
@@ -343,8 +343,8 @@ it('should handle status filter change', async () => { | |||
expect(wrapper.state().hotspots[0]).toBe(hotspots2[0]); | |||
// Set filter to FIXED | |||
wrapper.instance().handleChangeFilters({ status: HotspotStatusFilter.FIXED }); | |||
// Set filter to FIXED (use the other method to check this one): | |||
wrapper.instance().handleChangeStatusFilter(HotspotStatusFilter.FIXED); | |||
expect(getSecurityHotspots).toBeCalledWith( | |||
expect.objectContaining({ status: HotspotStatus.REVIEWED, resolution: HotspotResolution.FIXED }) |
@@ -144,6 +144,7 @@ function shallowRender(props: Partial<SecurityHotspotsAppRendererProps> = {}) { | |||
onHotspotClick={jest.fn()} | |||
onLoadMore={jest.fn()} | |||
onShowAllHotspots={jest.fn()} | |||
onSwitchStatusFilter={jest.fn()} | |||
onUpdateHotspot={jest.fn()} | |||
securityCategories={{}} | |||
selectedHotspot={undefined} |
@@ -49,6 +49,7 @@ exports[`should render correctly 1`] = ` | |||
onHotspotClick={[Function]} | |||
onLoadMore={[Function]} | |||
onShowAllHotspots={[Function]} | |||
onSwitchStatusFilter={[Function]} | |||
onUpdateHotspot={[Function]} | |||
securityCategories={Object {}} | |||
standards={ |
@@ -22,6 +22,7 @@ import { mockUser } from '../../../helpers/testMocks'; | |||
import { | |||
HotspotResolution, | |||
HotspotStatus, | |||
HotspotStatusFilter, | |||
HotspotStatusOption, | |||
ReviewHistoryType, | |||
RiskExposure | |||
@@ -29,6 +30,7 @@ import { | |||
import { | |||
getHotspotReviewHistory, | |||
getStatusAndResolutionFromStatusOption, | |||
getStatusFilterFromStatusOption, | |||
getStatusOptionFromStatusAndResolution, | |||
groupByCategory, | |||
mapRules, | |||
@@ -260,3 +262,17 @@ describe('getStatusAndResolutionFromStatusOption', () => { | |||
}); | |||
}); | |||
}); | |||
describe('getStatusFilterFromStatusOption', () => { | |||
it('should return the correct values', () => { | |||
expect(getStatusFilterFromStatusOption(HotspotStatusOption.TO_REVIEW)).toEqual( | |||
HotspotStatusFilter.TO_REVIEW | |||
); | |||
expect(getStatusFilterFromStatusOption(HotspotStatusOption.SAFE)).toEqual( | |||
HotspotStatusFilter.SAFE | |||
); | |||
expect(getStatusFilterFromStatusOption(HotspotStatusOption.FIXED)).toEqual( | |||
HotspotStatusFilter.FIXED | |||
); | |||
}); | |||
}); |
@@ -21,21 +21,30 @@ import * as React from 'react'; | |||
import { getSecurityHotspotDetails } from '../../../api/security-hotspots'; | |||
import { scrollToElement } from '../../../helpers/scrolling'; | |||
import { BranchLike } from '../../../types/branch-like'; | |||
import { Hotspot } from '../../../types/security-hotspots'; | |||
import { | |||
Hotspot, | |||
HotspotStatusFilter, | |||
HotspotStatusOption | |||
} from '../../../types/security-hotspots'; | |||
import { getStatusFilterFromStatusOption } from '../utils'; | |||
import HotspotViewerRenderer from './HotspotViewerRenderer'; | |||
interface Props { | |||
branchLike?: BranchLike; | |||
component: T.Component; | |||
hotspotKey: string; | |||
hotspotsReviewedMeasure?: string; | |||
onSwitchStatusFilter: (option: HotspotStatusFilter) => void; | |||
onUpdateHotspot: (hotspotKey: string) => Promise<void>; | |||
securityCategories: T.StandardSecurityCategories; | |||
} | |||
interface State { | |||
hotspot?: Hotspot; | |||
lastStatusChangedTo?: HotspotStatusOption; | |||
loading: boolean; | |||
commentVisible: boolean; | |||
showStatusUpdateSuccessModal: boolean; | |||
} | |||
export default class HotspotViewer extends React.PureComponent<Props, State> { | |||
@@ -46,7 +55,7 @@ export default class HotspotViewer extends React.PureComponent<Props, State> { | |||
constructor(props: Props) { | |||
super(props); | |||
this.commentTextRef = React.createRef<HTMLTextAreaElement>(); | |||
this.state = { loading: false, commentVisible: false }; | |||
this.state = { loading: false, commentVisible: false, showStatusUpdateSuccessModal: false }; | |||
} | |||
componentDidMount() { | |||
@@ -79,10 +88,11 @@ export default class HotspotViewer extends React.PureComponent<Props, State> { | |||
.catch(() => this.mounted && this.setState({ loading: false })); | |||
}; | |||
handleHotspotUpdate = async (statusUpdate = false) => { | |||
handleHotspotUpdate = async (statusUpdate = false, statusOption?: HotspotStatusOption) => { | |||
const { hotspotKey } = this.props; | |||
if (statusUpdate) { | |||
this.setState({ lastStatusChangedTo: statusOption, showStatusUpdateSuccessModal: true }); | |||
await this.props.onUpdateHotspot(hotspotKey); | |||
} else { | |||
await this.fetchHotspot(); | |||
@@ -106,9 +116,26 @@ export default class HotspotViewer extends React.PureComponent<Props, State> { | |||
this.setState({ commentVisible: false }); | |||
}; | |||
handleSwitchFilterToStatusOfUpdatedHotspot = () => { | |||
const { lastStatusChangedTo } = this.state; | |||
if (lastStatusChangedTo) { | |||
this.props.onSwitchStatusFilter(getStatusFilterFromStatusOption(lastStatusChangedTo)); | |||
} | |||
}; | |||
handleCloseStatusUpdateSuccessModal = () => { | |||
this.setState({ showStatusUpdateSuccessModal: false }); | |||
}; | |||
render() { | |||
const { branchLike, component, securityCategories } = this.props; | |||
const { hotspot, loading, commentVisible } = this.state; | |||
const { branchLike, component, hotspotsReviewedMeasure, securityCategories } = this.props; | |||
const { | |||
hotspot, | |||
lastStatusChangedTo, | |||
loading, | |||
commentVisible, | |||
showStatusUpdateSuccessModal | |||
} = this.state; | |||
return ( | |||
<HotspotViewerRenderer | |||
@@ -117,10 +144,15 @@ export default class HotspotViewer extends React.PureComponent<Props, State> { | |||
commentTextRef={this.commentTextRef} | |||
commentVisible={commentVisible} | |||
hotspot={hotspot} | |||
hotspotsReviewedMeasure={hotspotsReviewedMeasure} | |||
lastStatusChangedTo={lastStatusChangedTo} | |||
loading={loading} | |||
onCloseComment={this.handleCloseComment} | |||
onCloseStatusUpdateSuccessModal={this.handleCloseStatusUpdateSuccessModal} | |||
onOpenComment={this.handleOpenComment} | |||
onSwitchFilterToStatusOfUpdatedHotspot={this.handleSwitchFilterToStatusOfUpdatedHotspot} | |||
onUpdateHotspot={this.handleHotspotUpdate} | |||
showStatusUpdateSuccessModal={showStatusUpdateSuccessModal} | |||
securityCategories={securityCategories} | |||
/> | |||
); |
@@ -34,7 +34,7 @@ import { | |||
} from '../../../helpers/urls'; | |||
import { isLoggedIn } from '../../../helpers/users'; | |||
import { BranchLike } from '../../../types/branch-like'; | |||
import { Hotspot } from '../../../types/security-hotspots'; | |||
import { Hotspot, HotspotStatusOption } from '../../../types/security-hotspots'; | |||
import Assignee from './assignee/Assignee'; | |||
import HotspotOpenInIdeButton from './HotspotOpenInIdeButton'; | |||
import HotspotReviewHistoryAndComments from './HotspotReviewHistoryAndComments'; | |||
@@ -42,18 +42,24 @@ import HotspotSnippetContainer from './HotspotSnippetContainer'; | |||
import './HotspotViewer.css'; | |||
import HotspotViewerTabs from './HotspotViewerTabs'; | |||
import Status from './status/Status'; | |||
import StatusUpdateSuccessModal from './StatusUpdateSuccessModal'; | |||
export interface HotspotViewerRendererProps { | |||
branchLike?: BranchLike; | |||
component: T.Component; | |||
currentUser: T.CurrentUser; | |||
hotspot?: Hotspot; | |||
hotspotsReviewedMeasure?: string; | |||
lastStatusChangedTo?: HotspotStatusOption; | |||
loading: boolean; | |||
commentVisible: boolean; | |||
commentTextRef: React.RefObject<HTMLTextAreaElement>; | |||
onOpenComment: () => void; | |||
onCloseComment: () => void; | |||
onUpdateHotspot: (statusUpdate?: boolean) => Promise<void>; | |||
onCloseStatusUpdateSuccessModal: () => void; | |||
onUpdateHotspot: (statusUpdate?: boolean, statusOption?: HotspotStatusOption) => Promise<void>; | |||
onSwitchFilterToStatusOfUpdatedHotspot: () => void; | |||
showStatusUpdateSuccessModal: boolean; | |||
securityCategories: T.StandardSecurityCategories; | |||
} | |||
@@ -63,7 +69,10 @@ export function HotspotViewerRenderer(props: HotspotViewerRendererProps) { | |||
component, | |||
currentUser, | |||
hotspot, | |||
hotspotsReviewedMeasure, | |||
loading, | |||
lastStatusChangedTo, | |||
showStatusUpdateSuccessModal, | |||
securityCategories, | |||
commentTextRef, | |||
commentVisible | |||
@@ -79,6 +88,15 @@ export function HotspotViewerRenderer(props: HotspotViewerRendererProps) { | |||
return ( | |||
<DeferredSpinner className="big-spacer-left big-spacer-top" loading={loading}> | |||
{showStatusUpdateSuccessModal && ( | |||
<StatusUpdateSuccessModal | |||
hotspotsReviewedMeasure={hotspotsReviewedMeasure} | |||
lastStatusChangedTo={lastStatusChangedTo} | |||
onClose={props.onCloseStatusUpdateSuccessModal} | |||
onSwitchFilterToStatusOfUpdatedHotspot={props.onSwitchFilterToStatusOfUpdatedHotspot} | |||
/> | |||
)} | |||
{hotspot && ( | |||
<div className="big-padded hotspot-content"> | |||
<div className="huge-spacer-bottom display-flex-space-between"> | |||
@@ -142,7 +160,10 @@ export function HotspotViewerRenderer(props: HotspotViewerRendererProps) { | |||
</div> | |||
</div> | |||
<div className="huge-spacer-left abs-width-400"> | |||
<Status hotspot={hotspot} onStatusChange={() => props.onUpdateHotspot(true)} /> | |||
<Status | |||
hotspot={hotspot} | |||
onStatusChange={statusOption => props.onUpdateHotspot(true, statusOption)} | |||
/> | |||
</div> | |||
</div> | |||
@@ -0,0 +1,102 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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 classNames from 'classnames'; | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { Button, ButtonLink } from '../../../components/controls/buttons'; | |||
import Modal from '../../../components/controls/Modal'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { formatMeasure } from '../../../helpers/measures'; | |||
import { HotspotStatusOption } from '../../../types/security-hotspots'; | |||
export interface StatusUpdateSuccessModalProps { | |||
hotspotsReviewedMeasure?: string; | |||
lastStatusChangedTo?: HotspotStatusOption; | |||
onClose: () => void; | |||
onSwitchFilterToStatusOfUpdatedHotspot: () => void; | |||
} | |||
export default function StatusUpdateSuccessModal(props: StatusUpdateSuccessModalProps) { | |||
const { hotspotsReviewedMeasure, lastStatusChangedTo } = props; | |||
if (!lastStatusChangedTo) { | |||
return null; | |||
} | |||
const closingHotspots = lastStatusChangedTo !== HotspotStatusOption.TO_REVIEW; | |||
const statusLabel = translate('hotspots.status_option', lastStatusChangedTo); | |||
const modalTitle = closingHotspots | |||
? translate('hotspots.congratulations') | |||
: translate('hotspots.update.success'); | |||
return ( | |||
<Modal contentLabel={modalTitle}> | |||
<div className="modal-head"> | |||
<h2 | |||
className={classNames('huge text-normal', { | |||
'text-success': closingHotspots | |||
})}> | |||
{modalTitle} | |||
</h2> | |||
</div> | |||
<div className="modal-body"> | |||
<FormattedMessage | |||
id="hotspots.successfully_changed_to_x" | |||
defaultMessage={translate('hotspots.successfully_changed_to_x')} | |||
values={{ | |||
status_label: statusLabel, | |||
status_change: ( | |||
<strong> | |||
{translateWithParameters('hotspots.successful_status_change_to_x', statusLabel)} | |||
</strong> | |||
) | |||
}} | |||
/> | |||
{closingHotspots && ( | |||
<p className="spacer-top"> | |||
<FormattedMessage | |||
id="hotspots.x_done_keep_going" | |||
defaultMessage={translate('hotspots.x_done_keep_going')} | |||
values={{ | |||
percentage: ( | |||
<strong> | |||
{formatMeasure(hotspotsReviewedMeasure, 'PERCENT', { | |||
omitExtraDecimalZeros: true | |||
})} | |||
</strong> | |||
) | |||
}} | |||
/> | |||
</p> | |||
)} | |||
</div> | |||
<div className="modal-foot modal-foot-clear display-flex-center display-flex-space-between"> | |||
<ButtonLink onClick={props.onSwitchFilterToStatusOfUpdatedHotspot}> | |||
{translateWithParameters('hotspots.see_x_hotspots', statusLabel)} | |||
</ButtonLink> | |||
<Button className="button-primary padded" onClick={props.onClose}> | |||
{translate('hotspots.continue_to_next_hotspot')} | |||
</Button> | |||
</div> | |||
</Modal> | |||
); | |||
} |
@@ -24,6 +24,7 @@ import { getSecurityHotspotDetails } from '../../../../api/security-hotspots'; | |||
import { mockComponent } from '../../../../helpers/mocks/component'; | |||
import { scrollToElement } from '../../../../helpers/scrolling'; | |||
import { waitAndUpdate } from '../../../../helpers/testUtils'; | |||
import { HotspotStatusOption } from '../../../../types/security-hotspots'; | |||
import HotspotViewer from '../HotspotViewer'; | |||
import HotspotViewerRenderer from '../HotspotViewerRenderer'; | |||
@@ -63,6 +64,36 @@ it('should refresh hotspot list on status update', () => { | |||
expect(onUpdateHotspot).toHaveBeenCalled(); | |||
}); | |||
it('should store last status selected when updating a hotspot status', () => { | |||
const wrapper = shallowRender(); | |||
expect(wrapper.state().lastStatusChangedTo).toBeUndefined(); | |||
wrapper | |||
.find(HotspotViewerRenderer) | |||
.props() | |||
.onUpdateHotspot(true, HotspotStatusOption.FIXED); | |||
expect(wrapper.state().lastStatusChangedTo).toBe(HotspotStatusOption.FIXED); | |||
}); | |||
it('should correctly propagate a request to switch the status filter', () => { | |||
const onSwitchStatusFilter = jest.fn(); | |||
const wrapper = shallowRender({ onSwitchStatusFilter }); | |||
wrapper.instance().handleSwitchFilterToStatusOfUpdatedHotspot(); | |||
expect(onSwitchStatusFilter).not.toBeCalled(); | |||
wrapper.setState({ lastStatusChangedTo: HotspotStatusOption.FIXED }); | |||
wrapper.instance().handleSwitchFilterToStatusOfUpdatedHotspot(); | |||
expect(onSwitchStatusFilter).toBeCalledWith(HotspotStatusOption.FIXED); | |||
}); | |||
it('should correctly close the success modal', () => { | |||
const wrapper = shallowRender(); | |||
wrapper.setState({ showStatusUpdateSuccessModal: true }); | |||
wrapper.instance().handleCloseStatusUpdateSuccessModal(); | |||
expect(wrapper.state().showStatusUpdateSuccessModal).toBe(false); | |||
}); | |||
it('should NOT refresh hotspot list on assignee/comment updates', () => { | |||
const onUpdateHotspot = jest.fn(); | |||
const wrapper = shallowRender({ onUpdateHotspot }); | |||
@@ -118,6 +149,7 @@ function shallowRender(props?: Partial<HotspotViewer['props']>) { | |||
<HotspotViewer | |||
component={mockComponent()} | |||
hotspotKey={hotspotKey} | |||
onSwitchStatusFilter={jest.fn()} | |||
onUpdateHotspot={jest.fn()} | |||
securityCategories={{ cat1: { title: 'cat1' } }} | |||
{...props} |
@@ -23,13 +23,17 @@ import { mockBranch } from '../../../../helpers/mocks/branch-like'; | |||
import { mockComponent } from '../../../../helpers/mocks/component'; | |||
import { mockHotspot } from '../../../../helpers/mocks/security-hotspots'; | |||
import { mockCurrentUser, mockUser } from '../../../../helpers/testMocks'; | |||
import { HotspotStatusOption } from '../../../../types/security-hotspots'; | |||
import { HotspotViewerRenderer, HotspotViewerRendererProps } from '../HotspotViewerRenderer'; | |||
import Status from '../status/Status'; | |||
jest.mock('../../../../helpers/users', () => ({ isLoggedIn: jest.fn(() => true) })); | |||
it('should render correctly', () => { | |||
const wrapper = shallowRender(); | |||
expect(wrapper).toMatchSnapshot(); | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ showStatusUpdateSuccessModal: true })).toMatchSnapshot( | |||
'show success modal' | |||
); | |||
expect(shallowRender({ hotspot: undefined })).toMatchSnapshot('no hotspot'); | |||
expect(shallowRender({ hotspot: mockHotspot({ assignee: undefined }) })).toMatchSnapshot( | |||
'unassigned' | |||
@@ -47,6 +51,18 @@ it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('anonymous user'); | |||
}); | |||
it('correctly propagates the status change', () => { | |||
const onUpdateHotspot = jest.fn(); | |||
const wrapper = shallowRender({ onUpdateHotspot }); | |||
wrapper | |||
.find(Status) | |||
.props() | |||
.onStatusChange(HotspotStatusOption.FIXED); | |||
expect(onUpdateHotspot).toHaveBeenCalledWith(true, HotspotStatusOption.FIXED); | |||
}); | |||
function shallowRender(props?: Partial<HotspotViewerRendererProps>) { | |||
return shallow( | |||
<HotspotViewerRenderer | |||
@@ -56,11 +72,16 @@ function shallowRender(props?: Partial<HotspotViewerRendererProps>) { | |||
commentVisible={false} | |||
currentUser={mockCurrentUser()} | |||
hotspot={mockHotspot()} | |||
hotspotsReviewedMeasure="75" | |||
lastStatusChangedTo={HotspotStatusOption.FIXED} | |||
loading={false} | |||
onCloseComment={jest.fn()} | |||
onCloseStatusUpdateSuccessModal={jest.fn()} | |||
onOpenComment={jest.fn()} | |||
onSwitchFilterToStatusOfUpdatedHotspot={jest.fn()} | |||
onUpdateHotspot={jest.fn()} | |||
securityCategories={{ 'sql-injection': { title: 'SQL injection' } }} | |||
showStatusUpdateSuccessModal={false} | |||
{...props} | |||
/> | |||
); |
@@ -0,0 +1,45 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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 { HotspotStatusOption } from '../../../../types/security-hotspots'; | |||
import StatusUpdateSuccessModal, { | |||
StatusUpdateSuccessModalProps | |||
} from '../StatusUpdateSuccessModal'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ lastStatusChangedTo: HotspotStatusOption.TO_REVIEW })).toMatchSnapshot( | |||
'opening hotspots again' | |||
); | |||
expect(shallowRender({ lastStatusChangedTo: undefined }).type()).toBeNull(); | |||
}); | |||
function shallowRender(props: Partial<StatusUpdateSuccessModalProps> = {}) { | |||
return shallow<StatusUpdateSuccessModalProps>( | |||
<StatusUpdateSuccessModal | |||
onClose={jest.fn()} | |||
lastStatusChangedTo={HotspotStatusOption.FIXED} | |||
onSwitchFilterToStatusOfUpdatedHotspot={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -32,7 +32,9 @@ exports[`should render correctly 1`] = ` | |||
} | |||
loading={true} | |||
onCloseComment={[Function]} | |||
onCloseStatusUpdateSuccessModal={[Function]} | |||
onOpenComment={[Function]} | |||
onSwitchFilterToStatusOfUpdatedHotspot={[Function]} | |||
onUpdateHotspot={[Function]} | |||
securityCategories={ | |||
Object { | |||
@@ -41,6 +43,7 @@ exports[`should render correctly 1`] = ` | |||
}, | |||
} | |||
} | |||
showStatusUpdateSuccessModal={false} | |||
/> | |||
`; | |||
@@ -81,7 +84,9 @@ exports[`should render correctly 2`] = ` | |||
} | |||
loading={false} | |||
onCloseComment={[Function]} | |||
onCloseStatusUpdateSuccessModal={[Function]} | |||
onOpenComment={[Function]} | |||
onSwitchFilterToStatusOfUpdatedHotspot={[Function]} | |||
onUpdateHotspot={[Function]} | |||
securityCategories={ | |||
Object { | |||
@@ -90,5 +95,6 @@ exports[`should render correctly 2`] = ` | |||
}, | |||
} | |||
} | |||
showStatusUpdateSuccessModal={false} | |||
/> | |||
`; |
@@ -1,6 +1,6 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
exports[`should render correctly: anonymous user 1`] = ` | |||
<DeferredSpinner | |||
className="big-spacer-left big-spacer-top" | |||
loading={false} | |||
@@ -536,7 +536,7 @@ exports[`should render correctly 1`] = ` | |||
</DeferredSpinner> | |||
`; | |||
exports[`should render correctly: anonymous user 1`] = ` | |||
exports[`should render correctly: assignee without name 1`] = ` | |||
<DeferredSpinner | |||
className="big-spacer-left big-spacer-top" | |||
loading={false} | |||
@@ -664,8 +664,8 @@ exports[`should render correctly: anonymous user 1`] = ` | |||
"assigneeUser": Object { | |||
"active": true, | |||
"local": true, | |||
"login": "assignee", | |||
"name": "John Doe", | |||
"login": "assignee_login", | |||
"name": undefined, | |||
}, | |||
"author": "author", | |||
"authorUser": Object { | |||
@@ -744,8 +744,8 @@ exports[`should render correctly: anonymous user 1`] = ` | |||
"assigneeUser": Object { | |||
"active": true, | |||
"local": true, | |||
"login": "assignee", | |||
"name": "John Doe", | |||
"login": "assignee_login", | |||
"name": undefined, | |||
}, | |||
"author": "author", | |||
"authorUser": Object { | |||
@@ -842,8 +842,8 @@ exports[`should render correctly: anonymous user 1`] = ` | |||
"assigneeUser": Object { | |||
"active": true, | |||
"local": true, | |||
"login": "assignee", | |||
"name": "John Doe", | |||
"login": "assignee_login", | |||
"name": undefined, | |||
}, | |||
"author": "author", | |||
"authorUser": Object { | |||
@@ -915,8 +915,8 @@ exports[`should render correctly: anonymous user 1`] = ` | |||
"assigneeUser": Object { | |||
"active": true, | |||
"local": true, | |||
"login": "assignee", | |||
"name": "John Doe", | |||
"login": "assignee_login", | |||
"name": undefined, | |||
}, | |||
"author": "author", | |||
"authorUser": Object { | |||
@@ -999,8 +999,8 @@ exports[`should render correctly: anonymous user 1`] = ` | |||
"assigneeUser": Object { | |||
"active": true, | |||
"local": true, | |||
"login": "assignee", | |||
"name": "John Doe", | |||
"login": "assignee_login", | |||
"name": undefined, | |||
}, | |||
"author": "author", | |||
"authorUser": Object { | |||
@@ -1072,7 +1072,7 @@ exports[`should render correctly: anonymous user 1`] = ` | |||
</DeferredSpinner> | |||
`; | |||
exports[`should render correctly: assignee without name 1`] = ` | |||
exports[`should render correctly: default 1`] = ` | |||
<DeferredSpinner | |||
className="big-spacer-left big-spacer-top" | |||
loading={false} | |||
@@ -1200,8 +1200,8 @@ exports[`should render correctly: assignee without name 1`] = ` | |||
"assigneeUser": Object { | |||
"active": true, | |||
"local": true, | |||
"login": "assignee_login", | |||
"name": undefined, | |||
"login": "assignee", | |||
"name": "John Doe", | |||
}, | |||
"author": "author", | |||
"authorUser": Object { | |||
@@ -1280,8 +1280,8 @@ exports[`should render correctly: assignee without name 1`] = ` | |||
"assigneeUser": Object { | |||
"active": true, | |||
"local": true, | |||
"login": "assignee_login", | |||
"name": undefined, | |||
"login": "assignee", | |||
"name": "John Doe", | |||
}, | |||
"author": "author", | |||
"authorUser": Object { | |||
@@ -1378,8 +1378,8 @@ exports[`should render correctly: assignee without name 1`] = ` | |||
"assigneeUser": Object { | |||
"active": true, | |||
"local": true, | |||
"login": "assignee_login", | |||
"name": undefined, | |||
"login": "assignee", | |||
"name": "John Doe", | |||
}, | |||
"author": "author", | |||
"authorUser": Object { | |||
@@ -1451,8 +1451,8 @@ exports[`should render correctly: assignee without name 1`] = ` | |||
"assigneeUser": Object { | |||
"active": true, | |||
"local": true, | |||
"login": "assignee_login", | |||
"name": undefined, | |||
"login": "assignee", | |||
"name": "John Doe", | |||
}, | |||
"author": "author", | |||
"authorUser": Object { | |||
@@ -1535,8 +1535,8 @@ exports[`should render correctly: assignee without name 1`] = ` | |||
"assigneeUser": Object { | |||
"active": true, | |||
"local": true, | |||
"login": "assignee_login", | |||
"name": undefined, | |||
"login": "assignee", | |||
"name": "John Doe", | |||
}, | |||
"author": "author", | |||
"authorUser": Object { | |||
@@ -2151,6 +2151,548 @@ exports[`should render correctly: no hotspot 1`] = ` | |||
/> | |||
`; | |||
exports[`should render correctly: show success modal 1`] = ` | |||
<DeferredSpinner | |||
className="big-spacer-left big-spacer-top" | |||
loading={false} | |||
> | |||
<StatusUpdateSuccessModal | |||
hotspotsReviewedMeasure="75" | |||
lastStatusChangedTo="FIXED" | |||
onClose={[MockFunction]} | |||
onSwitchFilterToStatusOfUpdatedHotspot={[MockFunction]} | |||
/> | |||
<div | |||
className="big-padded hotspot-content" | |||
> | |||
<div | |||
className="huge-spacer-bottom display-flex-space-between" | |||
> | |||
<div | |||
className="display-flex-column" | |||
> | |||
<strong | |||
className="big big-spacer-right little-spacer-bottom" | |||
> | |||
'3' is a magic number. | |||
</strong> | |||
<div> | |||
<span | |||
className="note padded-right" | |||
> | |||
That rule | |||
</span> | |||
<Link | |||
className="small" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
target="_blank" | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
"query": Object { | |||
"open": "squid:S2077", | |||
"rule_key": "squid:S2077", | |||
}, | |||
} | |||
} | |||
> | |||
squid:S2077 | |||
</Link> | |||
</div> | |||
</div> | |||
<div | |||
className="display-flex-row flex-0" | |||
> | |||
<div | |||
className="dropdown spacer-right flex-1-0-auto" | |||
> | |||
<Button | |||
className="it__hs-add-comment" | |||
onClick={[MockFunction]} | |||
> | |||
hotspots.comment.open | |||
</Button> | |||
</div> | |||
<div | |||
className="dropdown spacer-right flex-1-0-auto" | |||
> | |||
<HotspotOpenInIdeButton | |||
hotspotKey="01fc972e-2a3c-433e-bcae-0bd7f88f5123" | |||
projectKey="hotspot-component" | |||
/> | |||
</div> | |||
<ClipboardButton | |||
className="flex-1-0-auto" | |||
copyValue="http://localhost/security_hotspots?id=my-project&branch=branch-6.7&hotspots=01fc972e-2a3c-433e-bcae-0bd7f88f5123" | |||
> | |||
<LinkIcon | |||
className="spacer-right" | |||
/> | |||
<span> | |||
hotspots.get_permalink | |||
</span> | |||
</ClipboardButton> | |||
</div> | |||
</div> | |||
<div | |||
className="huge-spacer-bottom display-flex-row display-flex-space-between" | |||
> | |||
<div | |||
className="hotspot-information display-flex-column display-flex-space-between" | |||
> | |||
<div | |||
className="display-flex-center" | |||
> | |||
<span | |||
className="big-spacer-right" | |||
> | |||
category | |||
</span> | |||
<strong | |||
className="nowrap" | |||
> | |||
SQL injection | |||
</strong> | |||
</div> | |||
<div | |||
className="display-flex-center" | |||
> | |||
<span | |||
className="big-spacer-right" | |||
> | |||
hotspots.risk_exposure | |||
</span> | |||
<div | |||
className="hotspot-risk-badge HIGH" | |||
> | |||
risk_exposure.HIGH | |||
</div> | |||
</div> | |||
<div | |||
className="display-flex-center it__hs-assignee" | |||
> | |||
<span | |||
className="big-spacer-right" | |||
> | |||
assignee | |||
</span> | |||
<div> | |||
<Connect(withCurrentUser(Assignee)) | |||
hotspot={ | |||
Object { | |||
"assignee": "assignee", | |||
"assigneeUser": Object { | |||
"active": true, | |||
"local": true, | |||
"login": "assignee", | |||
"name": "John Doe", | |||
}, | |||
"author": "author", | |||
"authorUser": Object { | |||
"active": true, | |||
"local": true, | |||
"login": "author", | |||
"name": "John Doe", | |||
}, | |||
"canChangeStatus": true, | |||
"changelog": Array [], | |||
"comment": Array [], | |||
"component": Object { | |||
"key": "hotspot-component", | |||
"longName": "Hotspot component long name", | |||
"name": "Hotspot Component", | |||
"path": "path/to/component", | |||
"qualifier": "FIL", | |||
}, | |||
"creationDate": "2013-05-13T17:55:41+0200", | |||
"key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", | |||
"line": 142, | |||
"message": "'3' is a magic number.", | |||
"project": Object { | |||
"key": "hotspot-component", | |||
"longName": "Hotspot component long name", | |||
"name": "Hotspot Component", | |||
"path": "path/to/component", | |||
"qualifier": "TRK", | |||
}, | |||
"resolution": "FIXED", | |||
"rule": Object { | |||
"fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", | |||
"key": "squid:S2077", | |||
"name": "That rule", | |||
"riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", | |||
"securityCategory": "sql-injection", | |||
"vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", | |||
"vulnerabilityProbability": "HIGH", | |||
}, | |||
"status": "REVIEWED", | |||
"textRange": Object { | |||
"endLine": 142, | |||
"endOffset": 83, | |||
"startLine": 142, | |||
"startOffset": 26, | |||
}, | |||
"updateDate": "2013-05-13T17:55:42+0200", | |||
"users": Array [ | |||
Object { | |||
"active": true, | |||
"local": true, | |||
"login": "assignee", | |||
"name": "John Doe", | |||
}, | |||
Object { | |||
"active": true, | |||
"local": true, | |||
"login": "author", | |||
"name": "John Doe", | |||
}, | |||
], | |||
} | |||
} | |||
onAssigneeChange={[MockFunction]} | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
<div | |||
className="huge-spacer-left abs-width-400" | |||
> | |||
<Connect(withCurrentUser(Status)) | |||
hotspot={ | |||
Object { | |||
"assignee": "assignee", | |||
"assigneeUser": Object { | |||
"active": true, | |||
"local": true, | |||
"login": "assignee", | |||
"name": "John Doe", | |||
}, | |||
"author": "author", | |||
"authorUser": Object { | |||
"active": true, | |||
"local": true, | |||
"login": "author", | |||
"name": "John Doe", | |||
}, | |||
"canChangeStatus": true, | |||
"changelog": Array [], | |||
"comment": Array [], | |||
"component": Object { | |||
"key": "hotspot-component", | |||
"longName": "Hotspot component long name", | |||
"name": "Hotspot Component", | |||
"path": "path/to/component", | |||
"qualifier": "FIL", | |||
}, | |||
"creationDate": "2013-05-13T17:55:41+0200", | |||
"key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", | |||
"line": 142, | |||
"message": "'3' is a magic number.", | |||
"project": Object { | |||
"key": "hotspot-component", | |||
"longName": "Hotspot component long name", | |||
"name": "Hotspot Component", | |||
"path": "path/to/component", | |||
"qualifier": "TRK", | |||
}, | |||
"resolution": "FIXED", | |||
"rule": Object { | |||
"fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", | |||
"key": "squid:S2077", | |||
"name": "That rule", | |||
"riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", | |||
"securityCategory": "sql-injection", | |||
"vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", | |||
"vulnerabilityProbability": "HIGH", | |||
}, | |||
"status": "REVIEWED", | |||
"textRange": Object { | |||
"endLine": 142, | |||
"endOffset": 83, | |||
"startLine": 142, | |||
"startOffset": 26, | |||
}, | |||
"updateDate": "2013-05-13T17:55:42+0200", | |||
"users": Array [ | |||
Object { | |||
"active": true, | |||
"local": true, | |||
"login": "assignee", | |||
"name": "John Doe", | |||
}, | |||
Object { | |||
"active": true, | |||
"local": true, | |||
"login": "author", | |||
"name": "John Doe", | |||
}, | |||
], | |||
} | |||
} | |||
onStatusChange={[Function]} | |||
/> | |||
</div> | |||
</div> | |||
<HotspotSnippetContainer | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"qualifier": "TRK", | |||
"qualityGate": Object { | |||
"isDefault": true, | |||
"key": "30", | |||
"name": "Sonar way", | |||
}, | |||
"qualityProfiles": Array [ | |||
Object { | |||
"deleted": false, | |||
"key": "my-qp", | |||
"language": "ts", | |||
"name": "Sonar way", | |||
}, | |||
], | |||
"tags": Array [], | |||
} | |||
} | |||
hotspot={ | |||
Object { | |||
"assignee": "assignee", | |||
"assigneeUser": Object { | |||
"active": true, | |||
"local": true, | |||
"login": "assignee", | |||
"name": "John Doe", | |||
}, | |||
"author": "author", | |||
"authorUser": Object { | |||
"active": true, | |||
"local": true, | |||
"login": "author", | |||
"name": "John Doe", | |||
}, | |||
"canChangeStatus": true, | |||
"changelog": Array [], | |||
"comment": Array [], | |||
"component": Object { | |||
"key": "hotspot-component", | |||
"longName": "Hotspot component long name", | |||
"name": "Hotspot Component", | |||
"path": "path/to/component", | |||
"qualifier": "FIL", | |||
}, | |||
"creationDate": "2013-05-13T17:55:41+0200", | |||
"key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", | |||
"line": 142, | |||
"message": "'3' is a magic number.", | |||
"project": Object { | |||
"key": "hotspot-component", | |||
"longName": "Hotspot component long name", | |||
"name": "Hotspot Component", | |||
"path": "path/to/component", | |||
"qualifier": "TRK", | |||
}, | |||
"resolution": "FIXED", | |||
"rule": Object { | |||
"fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", | |||
"key": "squid:S2077", | |||
"name": "That rule", | |||
"riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", | |||
"securityCategory": "sql-injection", | |||
"vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", | |||
"vulnerabilityProbability": "HIGH", | |||
}, | |||
"status": "REVIEWED", | |||
"textRange": Object { | |||
"endLine": 142, | |||
"endOffset": 83, | |||
"startLine": 142, | |||
"startOffset": 26, | |||
}, | |||
"updateDate": "2013-05-13T17:55:42+0200", | |||
"users": Array [ | |||
Object { | |||
"active": true, | |||
"local": true, | |||
"login": "assignee", | |||
"name": "John Doe", | |||
}, | |||
Object { | |||
"active": true, | |||
"local": true, | |||
"login": "author", | |||
"name": "John Doe", | |||
}, | |||
], | |||
} | |||
} | |||
/> | |||
<HotspotViewerTabs | |||
hotspot={ | |||
Object { | |||
"assignee": "assignee", | |||
"assigneeUser": Object { | |||
"active": true, | |||
"local": true, | |||
"login": "assignee", | |||
"name": "John Doe", | |||
}, | |||
"author": "author", | |||
"authorUser": Object { | |||
"active": true, | |||
"local": true, | |||
"login": "author", | |||
"name": "John Doe", | |||
}, | |||
"canChangeStatus": true, | |||
"changelog": Array [], | |||
"comment": Array [], | |||
"component": Object { | |||
"key": "hotspot-component", | |||
"longName": "Hotspot component long name", | |||
"name": "Hotspot Component", | |||
"path": "path/to/component", | |||
"qualifier": "FIL", | |||
}, | |||
"creationDate": "2013-05-13T17:55:41+0200", | |||
"key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", | |||
"line": 142, | |||
"message": "'3' is a magic number.", | |||
"project": Object { | |||
"key": "hotspot-component", | |||
"longName": "Hotspot component long name", | |||
"name": "Hotspot Component", | |||
"path": "path/to/component", | |||
"qualifier": "TRK", | |||
}, | |||
"resolution": "FIXED", | |||
"rule": Object { | |||
"fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", | |||
"key": "squid:S2077", | |||
"name": "That rule", | |||
"riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", | |||
"securityCategory": "sql-injection", | |||
"vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", | |||
"vulnerabilityProbability": "HIGH", | |||
}, | |||
"status": "REVIEWED", | |||
"textRange": Object { | |||
"endLine": 142, | |||
"endOffset": 83, | |||
"startLine": 142, | |||
"startOffset": 26, | |||
}, | |||
"updateDate": "2013-05-13T17:55:42+0200", | |||
"users": Array [ | |||
Object { | |||
"active": true, | |||
"local": true, | |||
"login": "assignee", | |||
"name": "John Doe", | |||
}, | |||
Object { | |||
"active": true, | |||
"local": true, | |||
"login": "author", | |||
"name": "John Doe", | |||
}, | |||
], | |||
} | |||
} | |||
/> | |||
<HotspotReviewHistoryAndComments | |||
commentTextRef={ | |||
Object { | |||
"current": null, | |||
} | |||
} | |||
commentVisible={false} | |||
currentUser={ | |||
Object { | |||
"isLoggedIn": false, | |||
} | |||
} | |||
hotspot={ | |||
Object { | |||
"assignee": "assignee", | |||
"assigneeUser": Object { | |||
"active": true, | |||
"local": true, | |||
"login": "assignee", | |||
"name": "John Doe", | |||
}, | |||
"author": "author", | |||
"authorUser": Object { | |||
"active": true, | |||
"local": true, | |||
"login": "author", | |||
"name": "John Doe", | |||
}, | |||
"canChangeStatus": true, | |||
"changelog": Array [], | |||
"comment": Array [], | |||
"component": Object { | |||
"key": "hotspot-component", | |||
"longName": "Hotspot component long name", | |||
"name": "Hotspot Component", | |||
"path": "path/to/component", | |||
"qualifier": "FIL", | |||
}, | |||
"creationDate": "2013-05-13T17:55:41+0200", | |||
"key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", | |||
"line": 142, | |||
"message": "'3' is a magic number.", | |||
"project": Object { | |||
"key": "hotspot-component", | |||
"longName": "Hotspot component long name", | |||
"name": "Hotspot Component", | |||
"path": "path/to/component", | |||
"qualifier": "TRK", | |||
}, | |||
"resolution": "FIXED", | |||
"rule": Object { | |||
"fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", | |||
"key": "squid:S2077", | |||
"name": "That rule", | |||
"riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", | |||
"securityCategory": "sql-injection", | |||
"vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", | |||
"vulnerabilityProbability": "HIGH", | |||
}, | |||
"status": "REVIEWED", | |||
"textRange": Object { | |||
"endLine": 142, | |||
"endOffset": 83, | |||
"startLine": 142, | |||
"startOffset": 26, | |||
}, | |||
"updateDate": "2013-05-13T17:55:42+0200", | |||
"users": Array [ | |||
Object { | |||
"active": true, | |||
"local": true, | |||
"login": "assignee", | |||
"name": "John Doe", | |||
}, | |||
Object { | |||
"active": true, | |||
"local": true, | |||
"login": "author", | |||
"name": "John Doe", | |||
}, | |||
], | |||
} | |||
} | |||
onCloseComment={[MockFunction]} | |||
onCommentUpdate={[MockFunction]} | |||
onOpenComment={[MockFunction]} | |||
/> | |||
</div> | |||
</DeferredSpinner> | |||
`; | |||
exports[`should render correctly: unassigned 1`] = ` | |||
<DeferredSpinner | |||
className="big-spacer-left big-spacer-top" |
@@ -0,0 +1,110 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<Modal | |||
contentLabel="hotspots.congratulations" | |||
> | |||
<div | |||
className="modal-head" | |||
> | |||
<h2 | |||
className="huge text-normal text-success" | |||
> | |||
hotspots.congratulations | |||
</h2> | |||
</div> | |||
<div | |||
className="modal-body" | |||
> | |||
<FormattedMessage | |||
defaultMessage="hotspots.successfully_changed_to_x" | |||
id="hotspots.successfully_changed_to_x" | |||
values={ | |||
Object { | |||
"status_change": <strong> | |||
hotspots.successful_status_change_to_x.hotspots.status_option.FIXED | |||
</strong>, | |||
"status_label": "hotspots.status_option.FIXED", | |||
} | |||
} | |||
/> | |||
<p | |||
className="spacer-top" | |||
> | |||
<FormattedMessage | |||
defaultMessage="hotspots.x_done_keep_going" | |||
id="hotspots.x_done_keep_going" | |||
values={ | |||
Object { | |||
"percentage": <strong> | |||
</strong>, | |||
} | |||
} | |||
/> | |||
</p> | |||
</div> | |||
<div | |||
className="modal-foot modal-foot-clear display-flex-center display-flex-space-between" | |||
> | |||
<ButtonLink | |||
onClick={[MockFunction]} | |||
> | |||
hotspots.see_x_hotspots.hotspots.status_option.FIXED | |||
</ButtonLink> | |||
<Button | |||
className="button-primary padded" | |||
onClick={[MockFunction]} | |||
> | |||
hotspots.continue_to_next_hotspot | |||
</Button> | |||
</div> | |||
</Modal> | |||
`; | |||
exports[`should render correctly: opening hotspots again 1`] = ` | |||
<Modal | |||
contentLabel="hotspots.update.success" | |||
> | |||
<div | |||
className="modal-head" | |||
> | |||
<h2 | |||
className="huge text-normal" | |||
> | |||
hotspots.update.success | |||
</h2> | |||
</div> | |||
<div | |||
className="modal-body" | |||
> | |||
<FormattedMessage | |||
defaultMessage="hotspots.successfully_changed_to_x" | |||
id="hotspots.successfully_changed_to_x" | |||
values={ | |||
Object { | |||
"status_change": <strong> | |||
hotspots.successful_status_change_to_x.hotspots.status_option.TO_REVIEW | |||
</strong>, | |||
"status_label": "hotspots.status_option.TO_REVIEW", | |||
} | |||
} | |||
/> | |||
</div> | |||
<div | |||
className="modal-foot modal-foot-clear display-flex-center display-flex-space-between" | |||
> | |||
<ButtonLink | |||
onClick={[MockFunction]} | |||
> | |||
hotspots.see_x_hotspots.hotspots.status_option.TO_REVIEW | |||
</ButtonLink> | |||
<Button | |||
className="button-primary padded" | |||
onClick={[MockFunction]} | |||
> | |||
hotspots.continue_to_next_hotspot | |||
</Button> | |||
</div> | |||
</Modal> | |||
`; |
@@ -28,7 +28,7 @@ import DropdownIcon from '../../../../components/icons/DropdownIcon'; | |||
import { PopupPlacement } from '../../../../components/ui/popups'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import { isLoggedIn } from '../../../../helpers/users'; | |||
import { Hotspot } from '../../../../types/security-hotspots'; | |||
import { Hotspot, HotspotStatusOption } from '../../../../types/security-hotspots'; | |||
import { getStatusOptionFromStatusAndResolution } from '../../utils'; | |||
import StatusDescription from './StatusDescription'; | |||
import StatusSelection from './StatusSelection'; | |||
@@ -37,7 +37,7 @@ export interface StatusProps { | |||
currentUser: T.CurrentUser; | |||
hotspot: Hotspot; | |||
onStatusChange: () => Promise<void>; | |||
onStatusChange: (statusOption: HotspotStatusOption) => Promise<void>; | |||
} | |||
export function Status(props: StatusProps) { | |||
@@ -64,8 +64,8 @@ export function Status(props: StatusProps) { | |||
<DropdownOverlay noPadding={true} placement={PopupPlacement.Bottom}> | |||
<StatusSelection | |||
hotspot={hotspot} | |||
onStatusOptionChange={async () => { | |||
await props.onStatusChange(); | |||
onStatusOptionChange={async status => { | |||
await props.onStatusChange(status); | |||
setIsOpen(false); | |||
}} | |||
/> |
@@ -19,8 +19,6 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import { setSecurityHotspotStatus } from '../../../../api/security-hotspots'; | |||
import addGlobalSuccessMessage from '../../../../app/utils/addGlobalSuccessMessage'; | |||
import { translate, translateWithParameters } from '../../../../helpers/l10n'; | |||
import { Hotspot, HotspotStatusOption } from '../../../../types/security-hotspots'; | |||
import { | |||
getStatusAndResolutionFromStatusOption, | |||
@@ -88,14 +86,6 @@ export default class StatusSelection extends React.PureComponent<Props, State> { | |||
await this.props.onStatusOptionChange(selectedStatus); | |||
this.setState({ loading: false }); | |||
}) | |||
.then(() => | |||
addGlobalSuccessMessage( | |||
translateWithParameters( | |||
'hotspots.update.success', | |||
translate('hotspots.status_option', selectedStatus) | |||
) | |||
) | |||
) | |||
.catch(() => this.setState({ loading: false })); | |||
} | |||
}; |
@@ -29,6 +29,7 @@ import { | |||
Hotspot, | |||
HotspotResolution, | |||
HotspotStatus, | |||
HotspotStatusFilter, | |||
HotspotStatusOption, | |||
RawHotspot, | |||
ReviewHistoryElement, | |||
@@ -181,3 +182,13 @@ const STATUS_OPTION_TO_STATUS_AND_RESOLUTION_MAP = { | |||
export function getStatusAndResolutionFromStatusOption(statusOption: HotspotStatusOption) { | |||
return STATUS_OPTION_TO_STATUS_AND_RESOLUTION_MAP[statusOption]; | |||
} | |||
const STATUS_OPTION_TO_STATUS_FILTER = { | |||
[HotspotStatusOption.TO_REVIEW]: HotspotStatusFilter.TO_REVIEW, | |||
[HotspotStatusOption.FIXED]: HotspotStatusFilter.FIXED, | |||
[HotspotStatusOption.SAFE]: HotspotStatusFilter.SAFE | |||
}; | |||
export function getStatusFilterFromStatusOption(statusOption: HotspotStatusOption) { | |||
return STATUS_OPTION_TO_STATUS_FILTER[statusOption]; | |||
} |
@@ -209,3 +209,15 @@ | |||
.modal-foot input[type='button'] { | |||
margin-left: var(--gridSize); | |||
} | |||
.modal-foot button:first-of-type, | |||
.modal-foot .button:first-of-type, | |||
.modal-foot input[type='submit']:first-of-type, | |||
.modal-foot input[type='button']:first-of-type { | |||
margin-left: 0; | |||
} | |||
.modal-foot-clear { | |||
border-top: 0; | |||
background-color: transparent; | |||
} |
@@ -768,6 +768,12 @@ hotspots.status_option.SAFE=Safe | |||
hotspots.status_option.SAFE.description=The code is not at risk and doesn't need to be modified. | |||
hotspots.get_permalink=Get Permalink | |||
hotspots.no_associated_lines=Security Hotspot raised on the following file: | |||
hotspots.congratulations=Congratulations! | |||
hotspots.successfully_changed_to_x=The Security Hotspot was {status_change}. You can find it by changing the top filter to display "{status_label}" Security Hotspots. | |||
hotspots.successful_status_change_to_x=successfully changed to "{0}" | |||
hotspots.x_done_keep_going={percentage} of the Security Hotspots have been reviewed, keep going! | |||
hotspots.see_x_hotspots=See "{0}" Security Hotspots | |||
hotspots.continue_to_next_hotspot=Continue reviewing next Security Hotspot | |||
hotspot.filters.title=Filters | |||
hotspot.filters.assignee.assigned_to_me=Assigned to me | |||
@@ -785,7 +791,7 @@ hotspots.review_hotspot=Review Hotspot | |||
hotspots.assign.success=Security Hotspot was successfully assigned to {0} | |||
hotspots.assign.unassign.success=Security Hotspot was successfully unassigned | |||
hotspots.update.success=Security Hotspot status was successfully changed to {0} | |||
hotspots.update.success=Update successful | |||
#------------------------------------------------------------------------------ | |||
# |