@@ -29,7 +29,7 @@ import { updateIssue } from '../actions'; | |||
interface Props { | |||
hasTransitions: boolean; | |||
isOpen: boolean; | |||
issue: Pick<T.Issue, 'key' | 'resolution' | 'status' | 'transitions'>; | |||
issue: Pick<T.Issue, 'fromHotspot' | 'key' | 'resolution' | 'status' | 'transitions' | 'type'>; | |||
onChange: (issue: T.Issue) => void; | |||
togglePopup: (popup: string, show?: boolean) => void; | |||
} | |||
@@ -61,7 +61,12 @@ export default class IssueTransition extends React.PureComponent<Props> { | |||
onRequestClose={this.handleClose} | |||
open={this.props.isOpen && this.props.hasTransitions} | |||
overlay={ | |||
<SetTransitionPopup onSelect={this.setTransition} transitions={issue.transitions} /> | |||
<SetTransitionPopup | |||
fromHotspot={issue.fromHotspot} | |||
onSelect={this.setTransition} | |||
transitions={issue.transitions} | |||
type={issue.type} | |||
/> | |||
}> | |||
<ButtonLink | |||
className="issue-action issue-action-with-options js-issue-transition" |
@@ -22,17 +22,19 @@ import { shallow } from 'enzyme'; | |||
import IssueTransition from '../IssueTransition'; | |||
import { click } from '../../../../helpers/testUtils'; | |||
const issue = { | |||
const issue: IssueTransition['props']['issue'] = { | |||
fromHotspot: false, | |||
key: 'foo1234', | |||
transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'], | |||
status: 'OPEN' | |||
status: 'OPEN', | |||
type: 'BUG' | |||
}; | |||
it('should render without the action when there is no transitions', () => { | |||
expect( | |||
shallowRender({ | |||
hasTransitions: false, | |||
issue: { key: 'foo1234', transitions: [], status: 'CLOSED' } | |||
issue: { fromHotspot: false, key: 'foo1234', transitions: [], status: 'CLOSED', type: 'BUG' } | |||
}) | |||
).toMatchSnapshot(); | |||
}); | |||
@@ -45,10 +47,12 @@ it('should render with a resolution', () => { | |||
expect( | |||
shallowRender({ | |||
issue: { | |||
fromHotspot: false, | |||
key: 'foo1234', | |||
transitions: ['reopen'], | |||
status: 'RESOLVED', | |||
resolution: 'FIXED' | |||
resolution: 'FIXED', | |||
type: 'BUG' | |||
} | |||
}) | |||
).toMatchSnapshot(); |
@@ -18,6 +18,7 @@ exports[`should open the popup when the button is clicked 2`] = ` | |||
open={true} | |||
overlay={ | |||
<SetTransitionPopup | |||
fromHotspot={false} | |||
onSelect={[Function]} | |||
transitions={ | |||
Array [ | |||
@@ -27,6 +28,7 @@ exports[`should open the popup when the button is clicked 2`] = ` | |||
"wontfix", | |||
] | |||
} | |||
type="BUG" | |||
/> | |||
} | |||
> | |||
@@ -55,12 +57,14 @@ exports[`should render with a resolution 1`] = ` | |||
open={false} | |||
overlay={ | |||
<SetTransitionPopup | |||
fromHotspot={false} | |||
onSelect={[Function]} | |||
transitions={ | |||
Array [ | |||
"reopen", | |||
] | |||
} | |||
type="BUG" | |||
/> | |||
} | |||
> | |||
@@ -90,6 +94,7 @@ exports[`should render with the action 1`] = ` | |||
open={false} | |||
overlay={ | |||
<SetTransitionPopup | |||
fromHotspot={false} | |||
onSelect={[Function]} | |||
transitions={ | |||
Array [ | |||
@@ -99,6 +104,7 @@ exports[`should render with the action 1`] = ` | |||
"wontfix", | |||
] | |||
} | |||
type="BUG" | |||
/> | |||
} | |||
> |
@@ -20,25 +20,26 @@ | |||
import * as React from 'react'; | |||
import SelectList from '../../common/SelectList'; | |||
import SelectListItem from '../../common/SelectListItem'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { translate, hasMessage } from '../../../helpers/l10n'; | |||
import { DropdownOverlay } from '../../controls/Dropdown'; | |||
interface Props { | |||
export interface Props { | |||
fromHotspot: boolean; | |||
onSelect: (transition: string) => void; | |||
transitions: string[]; | |||
type: T.IssueType; | |||
} | |||
export default function SetTransitionPopup({ onSelect, transitions }: Props) { | |||
export default function SetTransitionPopup({ fromHotspot, onSelect, transitions, type }: Props) { | |||
const isManualVulnerability = fromHotspot && type === 'VULNERABILITY'; | |||
return ( | |||
<DropdownOverlay> | |||
<SelectList currentItem={transitions[0]} items={transitions} onSelect={onSelect}> | |||
{transitions.map(transition => { | |||
const [name, description] = translateTransition(transition, isManualVulnerability); | |||
return ( | |||
<SelectListItem | |||
item={transition} | |||
key={transition} | |||
title={translate('issue.transition', transition, 'description')}> | |||
{translate('issue.transition', transition)} | |||
<SelectListItem item={transition} key={transition} title={description}> | |||
{name} | |||
</SelectListItem> | |||
); | |||
})} | |||
@@ -46,3 +47,15 @@ export default function SetTransitionPopup({ onSelect, transitions }: Props) { | |||
</DropdownOverlay> | |||
); | |||
} | |||
function translateTransition(transition: string, isManualVulnerability: boolean) { | |||
return isManualVulnerability && hasMessage('vulnerability.transition', transition) | |||
? [ | |||
translate('vulnerability.transition', transition), | |||
translate('vulnerability.transition', transition, 'description') | |||
] | |||
: [ | |||
translate('issue.transition', transition), | |||
translate('issue.transition', transition, 'description') | |||
]; | |||
} |
@@ -19,14 +19,36 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import SetTransitionPopup from '../SetTransitionPopup'; | |||
import SetTransitionPopup, { Props } from '../SetTransitionPopup'; | |||
import { hasMessage } from '../../../../helpers/l10n'; | |||
it('should render tags popup correctly', () => { | |||
const element = shallow( | |||
jest.mock('../../../../helpers/l10n', () => ({ | |||
...jest.requireActual('../../../../helpers/l10n'), | |||
hasMessage: jest.fn().mockReturnValue(false) | |||
})); | |||
it('should render transition popup correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
}); | |||
it('should render transition popup correctly for vulnerability', () => { | |||
(hasMessage as jest.Mock).mockReturnValueOnce('true'); | |||
expect( | |||
shallowRender({ | |||
fromHotspot: true, | |||
transitions: ['resolveasreviewed', 'confirm'] | |||
}) | |||
).toMatchSnapshot(); | |||
}); | |||
function shallowRender(props: Partial<Props> = {}) { | |||
return shallow( | |||
<SetTransitionPopup | |||
fromHotspot={false} | |||
onSelect={jest.fn()} | |||
transitions={['confirm', 'resolve', 'falsepositive', 'wontfix']} | |||
type="VULNERABILITY" | |||
{...props} | |||
/> | |||
); | |||
expect(element).toMatchSnapshot(); | |||
}); | |||
} |
@@ -1,6 +1,6 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render tags popup correctly 1`] = ` | |||
exports[`should render transition popup correctly 1`] = ` | |||
<DropdownOverlay> | |||
<SelectList | |||
currentItem="confirm" | |||
@@ -45,3 +45,33 @@ exports[`should render tags popup correctly 1`] = ` | |||
</SelectList> | |||
</DropdownOverlay> | |||
`; | |||
exports[`should render transition popup correctly for vulnerability 1`] = ` | |||
<DropdownOverlay> | |||
<SelectList | |||
currentItem="resolveasreviewed" | |||
items={ | |||
Array [ | |||
"resolveasreviewed", | |||
"confirm", | |||
] | |||
} | |||
onSelect={[MockFunction]} | |||
> | |||
<SelectListItem | |||
item="resolveasreviewed" | |||
key="resolveasreviewed" | |||
title="vulnerability.transition.resolveasreviewed.description" | |||
> | |||
vulnerability.transition.resolveasreviewed | |||
</SelectListItem> | |||
<SelectListItem | |||
item="confirm" | |||
key="confirm" | |||
title="issue.transition.confirm.description" | |||
> | |||
issue.transition.confirm | |||
</SelectListItem> | |||
</SelectList> | |||
</DropdownOverlay> | |||
`; |
@@ -618,14 +618,19 @@ issue.transition.close=Close | |||
issue.transition.close.description= | |||
issue.transition.wontfix=Resolve as won't fix | |||
issue.transition.wontfix.description=This issue can be ignored because the rule is irrelevant in this context. Its effort won't be counted. | |||
issue.transition.setinreview = Set as In Review | |||
issue.transition.setinreview.description = A review is required to check for a vulnerability | |||
issue.transition.resolveasreviewed = Resolve as Reviewed | |||
issue.transition.resolveasreviewed.description = There is no vulnerability in the code | |||
issue.transition.openasvulnerability = Open as Vulnerability | |||
issue.transition.openasvulnerability.description = There's a vulnerability in the code that must be fixed | |||
issue.transition.resetastoreview = Reset as security hotspot To Review | |||
issue.transition.resetastoreview.description = The security hotspot should be analyzed again | |||
issue.transition.setinreview=Set as In Review | |||
issue.transition.setinreview.description=A review is required to check for a Vulnerability | |||
issue.transition.openasvulnerability=Open as Vulnerability | |||
issue.transition.openasvulnerability.description=There's a Vulnerability in the code that must be fixed | |||
issue.transition.resolveasreviewed=Resolve as Reviewed | |||
issue.transition.resolveasreviewed.description=There is no Vulnerability in the code | |||
issue.transition.resetastoreview=Reset as To Review | |||
issue.transition.resetastoreview.description=The Security Hotspot should be analyzed again | |||
vulnerability.transition.resetastoreview=Reset as Security Hotspot To Review | |||
vulnerability.transition.resetastoreview.description=The Vulnerability can't be fixed as is and needs more details | |||
vulnerability.transition.resolveasreviewed=Resolve as Reviewed Security Hotspot | |||
vulnerability.transition.resolveasreviewed.description=The Vulnerability has been fixed | |||
issue.set_severity=Change Severity | |||
issue.set_type=Change Type | |||