@@ -67,7 +67,10 @@ export class Dropdown extends React.PureComponent<Readonly<Props>, State> { | |||
if (!prevState.open && this.state.open && this.props.onOpen) { | |||
this.props.onOpen(); | |||
} | |||
if (props.openDropdown !== this.props.openDropdown && this.props.openDropdown) { | |||
if ( | |||
props.openDropdown !== this.props.openDropdown && | |||
typeof this.props.openDropdown === 'boolean' | |||
) { | |||
this.setState({ open: this.props.openDropdown }); | |||
} | |||
} |
@@ -68,6 +68,7 @@ interface ListItemProps { | |||
onFocus?: VoidFunction; | |||
onPointerEnter?: VoidFunction; | |||
onPointerLeave?: VoidFunction; | |||
selected?: boolean; | |||
} | |||
type ItemLinkProps = Omit<ListItemProps, 'innerRef'> & | |||
@@ -76,12 +77,22 @@ type ItemLinkProps = Omit<ListItemProps, 'innerRef'> & | |||
}; | |||
export function ItemLink(props: ItemLinkProps) { | |||
const { children, className, disabled, icon, isExternal, onClick, innerRef, to, ...liProps } = | |||
props; | |||
const { | |||
children, | |||
className, | |||
disabled, | |||
icon, | |||
isExternal, | |||
onClick, | |||
selected, | |||
innerRef, | |||
to, | |||
...liProps | |||
} = props; | |||
return ( | |||
<li {...liProps}> | |||
<ItemLinkStyled | |||
className={classNames(className, { disabled })} | |||
className={classNames(className, { disabled, selected })} | |||
disabled={disabled} | |||
icon={icon} | |||
isExternal={isExternal} | |||
@@ -102,11 +113,12 @@ interface ItemNavLinkProps extends ItemLinkProps { | |||
} | |||
export function ItemNavLink(props: ItemNavLinkProps) { | |||
const { children, className, disabled, end, icon, onClick, innerRef, to, ...liProps } = props; | |||
const { children, className, disabled, end, icon, onClick, selected, innerRef, to, ...liProps } = | |||
props; | |||
return ( | |||
<li {...liProps}> | |||
<ItemNavLinkStyled | |||
className={classNames(className, { disabled })} | |||
className={classNames(className, { disabled, selected })} | |||
disabled={disabled} | |||
end={end} | |||
onClick={onClick} | |||
@@ -128,10 +140,15 @@ interface ItemButtonProps extends ListItemProps { | |||
} | |||
export function ItemButton(props: ItemButtonProps) { | |||
const { children, className, disabled, icon, innerRef, onClick, ...liProps } = props; | |||
const { children, className, disabled, icon, innerRef, onClick, selected, ...liProps } = props; | |||
return ( | |||
<li ref={innerRef} role="none" {...liProps}> | |||
<ItemButtonStyled className={className} disabled={disabled} onClick={onClick} role="menuitem"> | |||
<ItemButtonStyled | |||
className={classNames(className, { disabled, selected })} | |||
disabled={disabled} | |||
onClick={onClick} | |||
role="menuitem" | |||
> | |||
{icon} | |||
{children} | |||
</ItemButtonStyled> | |||
@@ -336,6 +353,10 @@ const itemStyle = (props: ThemedProps) => css` | |||
${tw`sw-cursor-not-allowed`}; | |||
} | |||
&.selected { | |||
background-color: ${themeColor('selectOptionSelected')(props)}; | |||
} | |||
& > svg { | |||
${tw`sw-mr-2`} | |||
} |
@@ -104,7 +104,7 @@ export function SearchSelectDropdownControl(props: SearchSelectDropdownControlPr | |||
); | |||
} | |||
const StyledControl = styled.div` | |||
export const StyledControl = styled.div` | |||
color: ${themeContrast('inputBackground')}; | |||
background: ${themeColor('inputBackground')}; | |||
border: ${themeBorder('default', 'inputBorder')}; | |||
@@ -121,7 +121,7 @@ const StyledControl = styled.div` | |||
&.is-discreet { | |||
${tw`sw-border-none`}; | |||
${tw`sw-p-0`}; | |||
${tw`sw-px-1`}; | |||
${tw`sw-w-auto sw-h-auto`}; | |||
background: inherit; |
@@ -31,3 +31,4 @@ export * from './MultiSelectMenu'; | |||
export * from './RadioButton'; | |||
export * from './SearchSelect'; | |||
export * from './SearchSelectDropdown'; | |||
export * from './SearchSelectDropdownControl'; |
@@ -39,7 +39,7 @@ import { | |||
import { SearchRulesResponse } from '../../types/coding-rules'; | |||
import { | |||
ASSIGNEE_ME, | |||
IssueResolution, | |||
IssueSimpleStatus, | |||
IssueStatus, | |||
IssueTransition, | |||
IssueType, | |||
@@ -525,45 +525,39 @@ export default class IssuesServiceMock { | |||
}; | |||
handleSetIssueTransition = (data: { issue: string; transition: string }) => { | |||
const statusMap: { [key: string]: IssueStatus } = { | |||
[IssueTransition.Confirm]: IssueStatus.Confirmed, | |||
[IssueTransition.UnConfirm]: IssueStatus.Reopened, | |||
[IssueTransition.Resolve]: IssueStatus.Resolved, | |||
[IssueTransition.WontFix]: IssueStatus.Resolved, | |||
[IssueTransition.FalsePositive]: IssueStatus.Resolved, | |||
const simpleStatusMap: { [key: string]: IssueSimpleStatus } = { | |||
[IssueTransition.Accept]: IssueSimpleStatus.Accepted, | |||
[IssueTransition.Confirm]: IssueSimpleStatus.Confirmed, | |||
[IssueTransition.UnConfirm]: IssueSimpleStatus.Open, | |||
[IssueTransition.Resolve]: IssueSimpleStatus.Fixed, | |||
[IssueTransition.WontFix]: IssueSimpleStatus.Accepted, | |||
[IssueTransition.FalsePositive]: IssueSimpleStatus.FalsePositive, | |||
}; | |||
const transitionMap: Dict<IssueTransition[]> = { | |||
[IssueStatus.Reopened]: [ | |||
IssueTransition.Confirm, | |||
IssueTransition.Resolve, | |||
IssueTransition.FalsePositive, | |||
IssueTransition.WontFix, | |||
], | |||
[IssueStatus.Open]: [ | |||
[IssueSimpleStatus.Open]: [ | |||
IssueTransition.Accept, | |||
IssueTransition.Confirm, | |||
IssueTransition.Resolve, | |||
IssueTransition.FalsePositive, | |||
IssueTransition.WontFix, | |||
], | |||
[IssueStatus.Confirmed]: [ | |||
[IssueSimpleStatus.Confirmed]: [ | |||
IssueTransition.Accept, | |||
IssueTransition.Resolve, | |||
IssueTransition.UnConfirm, | |||
IssueTransition.FalsePositive, | |||
IssueTransition.WontFix, | |||
], | |||
[IssueStatus.Resolved]: [IssueTransition.Reopen], | |||
}; | |||
const resolutionMap: Dict<string> = { | |||
[IssueTransition.WontFix]: IssueResolution.WontFix, | |||
[IssueTransition.FalsePositive]: IssueResolution.FalsePositive, | |||
[IssueSimpleStatus.FalsePositive]: [IssueTransition.Reopen], | |||
[IssueSimpleStatus.Accepted]: [IssueTransition.Reopen], | |||
[IssueSimpleStatus.Fixed]: [IssueTransition.Reopen], | |||
}; | |||
return this.getActionsResponse( | |||
{ | |||
status: statusMap[data.transition], | |||
transitions: transitionMap[statusMap[data.transition]], | |||
resolution: resolutionMap[data.transition], | |||
simpleStatus: simpleStatusMap[data.transition], | |||
transitions: transitionMap[simpleStatusMap[data.transition]], | |||
}, | |||
data.issue, | |||
); |
@@ -31,7 +31,9 @@ import { | |||
IssueResolution, | |||
IssueScope, | |||
IssueSeverity, | |||
IssueSimpleStatus, | |||
IssueStatus, | |||
IssueTransition, | |||
IssueType, | |||
RawIssue, | |||
} from '../../../types/issues'; | |||
@@ -296,7 +298,13 @@ export function mockIssuesList(baseComponentKey = PARENT_COMPONENT_KEY): IssueDa | |||
issue: mockRawIssue(false, { | |||
key: ISSUE_2, | |||
actions: Object.values(IssueActions), | |||
transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'], | |||
transitions: [ | |||
IssueTransition.Accept, | |||
IssueTransition.Confirm, | |||
IssueTransition.Resolve, | |||
IssueTransition.FalsePositive, | |||
IssueTransition.WontFix, | |||
], | |||
component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_2][0]}`, | |||
message: 'Fix that', | |||
rule: ISSUE_TO_RULE[ISSUE_2], | |||
@@ -312,6 +320,7 @@ export function mockIssuesList(baseComponentKey = PARENT_COMPONENT_KEY): IssueDa | |||
ruleDescriptionContextKey: 'spring', | |||
resolution: IssueResolution.Unresolved, | |||
status: IssueStatus.Open, | |||
simpleStatus: IssueSimpleStatus.Open, | |||
}), | |||
snippets: keyBy( | |||
[ | |||
@@ -354,7 +363,12 @@ export function mockIssuesList(baseComponentKey = PARENT_COMPONENT_KEY): IssueDa | |||
issue: mockRawIssue(false, { | |||
key: ISSUE_4, | |||
actions: Object.values(IssueActions), | |||
transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'], | |||
transitions: [ | |||
IssueTransition.Confirm, | |||
IssueTransition.Resolve, | |||
IssueTransition.FalsePositive, | |||
IssueTransition.WontFix, | |||
], | |||
component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_4][0]}`, | |||
message: 'Issue with tags', | |||
rule: ISSUE_TO_RULE[ISSUE_4], |
@@ -154,36 +154,43 @@ describe('issue app', () => { | |||
// Get a specific issue list item | |||
const listItem = within(await screen.findByRole('region', { name: 'Fix that' })); | |||
// Change issue status | |||
expect(listItem.getByText('issue.status.OPEN')).toBeInTheDocument(); | |||
expect(listItem.getByText('issue.simple_status.OPEN')).toBeInTheDocument(); | |||
await act(async () => { | |||
await user.click(listItem.getByText('issue.status.OPEN')); | |||
await user.click(listItem.getByText('issue.simple_status.OPEN')); | |||
}); | |||
expect(listItem.getByText('issue.transition.accept')).toBeInTheDocument(); | |||
expect(listItem.getByText('issue.transition.confirm')).toBeInTheDocument(); | |||
expect(listItem.getByText('issue.transition.resolve')).toBeInTheDocument(); | |||
await act(async () => { | |||
await user.click(listItem.getByText('issue.transition.confirm')); | |||
}); | |||
expect( | |||
listItem.getByLabelText('issue.transition.status_x_click_to_change.issue.status.CONFIRMED'), | |||
).toBeInTheDocument(); | |||
// As won't fix | |||
expect(listItem.getByRole('textbox')).toBeInTheDocument(); | |||
await act(async () => { | |||
await user.click(listItem.getByText('issue.status.CONFIRMED')); | |||
await user.click(listItem.getByText('issue.transition.wontfix')); | |||
await user.type(listItem.getByRole('textbox'), 'test'); | |||
await user.click(listItem.getByText('resolve')); | |||
}); | |||
// Comment should open and close | |||
expect(listItem.getByRole('button', { name: 'issue.comment.formlink' })).toBeInTheDocument(); | |||
expect( | |||
listItem.getByLabelText( | |||
'issue.transition.status_x_click_to_change.issue.simple_status.CONFIRMED', | |||
), | |||
).toBeInTheDocument(); | |||
// Change status again | |||
await act(async () => { | |||
await user.keyboard('test'); | |||
await user.click(listItem.getByRole('button', { name: 'issue.comment.formlink' })); | |||
await user.click(listItem.getByText('issue.simple_status.CONFIRMED')); | |||
await user.click(listItem.getByText('issue.transition.accept')); | |||
await user.click(listItem.getByText('resolve')); | |||
}); | |||
expect( | |||
listItem.queryByRole('button', { name: 'issue.comment.submit' }), | |||
).not.toBeInTheDocument(); | |||
listItem.getByLabelText( | |||
'issue.transition.status_x_click_to_change.issue.simple_status.ACCEPTED', | |||
), | |||
).toBeInTheDocument(); | |||
// Assign issue to a different user | |||
await act(async () => { |
@@ -26,6 +26,7 @@ import CurrentUserContextProvider from '../../../../app/components/current-user/ | |||
import { mockIssue, mockLoggedInUser } from '../../../../helpers/testMocks'; | |||
import { renderComponent } from '../../../../helpers/testReactTestingUtils'; | |||
import { ComponentPropsType } from '../../../../helpers/testUtils'; | |||
import { IssueTransition } from '../../../../types/issues'; | |||
import { Issue } from '../../../../types/types'; | |||
import { CurrentUser } from '../../../../types/users'; | |||
import BulkChangeModal, { MAX_PAGE_SIZE } from '../BulkChangeModal'; | |||
@@ -70,11 +71,11 @@ it('should render tags correctly', async () => { | |||
it('should render transitions correctly', async () => { | |||
renderBulkChangeModal([ | |||
mockIssue(false, { actions: ['set_transition'], transitions: ['Transition1'] }), | |||
mockIssue(false, { actions: ['set_transition'], transitions: [IssueTransition.FalsePositive] }), | |||
]); | |||
expect(await screen.findByText('issue.transition')).toBeInTheDocument(); | |||
expect(await screen.findByText('issue.transition.Transition1')).toBeInTheDocument(); | |||
expect(await screen.findByText('issue.transition.falsepositive')).toBeInTheDocument(); | |||
}); | |||
it('should disable the submit button unless some change is configured', async () => { | |||
@@ -108,12 +109,12 @@ it('should properly submit', async () => { | |||
mockIssue(false, { | |||
actions: ['assign', 'set_transition', 'set_tags', 'set_type', 'set_severity', 'comment'], | |||
key: 'issue1', | |||
transitions: ['Transition1', 'Transition2'], | |||
transitions: [IssueTransition.Accept, IssueTransition.FalsePositive], | |||
}), | |||
mockIssue(false, { | |||
actions: ['assign', 'set_transition', 'set_tags', 'set_type', 'set_severity', 'comment'], | |||
key: 'issue2', | |||
transitions: ['Transition1', 'Transition2'], | |||
transitions: [IssueTransition.Accept, IssueTransition.FalsePositive], | |||
}), | |||
], | |||
{ | |||
@@ -136,7 +137,7 @@ it('should properly submit', async () => { | |||
await user.click(await screen.findByText('Toto')); | |||
// Transition | |||
await user.click(await screen.findByText('issue.transition.Transition2')); | |||
await user.click(await screen.findByText('issue.transition.accept')); | |||
// Add a tag | |||
await act(async () => { | |||
@@ -161,7 +162,7 @@ it('should properly submit', async () => { | |||
add_tags: 'tag1,tag2', | |||
assign: 'toto', | |||
comment: 'some comment', | |||
do_transition: 'Transition2', | |||
do_transition: 'accept', | |||
sendNotifications: true, | |||
}); | |||
}); |
@@ -68,7 +68,8 @@ export default function Assignee(props: Props) { | |||
const controlLabel = assigneeUser ? ( | |||
<> | |||
{renderAvatar(assigneeUser?.name, assigneeUser.avatar)} {assigneeUser.name} | |||
<Avatar hash={assigneeUser.avatar} name={assigneeUser?.name} size="xs" className="sw-mt-1" />{' '} | |||
{assigneeUser.name} | |||
</> | |||
) : ( | |||
UNASSIGNED.label |
@@ -33,6 +33,7 @@ exports[`loadIssues should load issues with listIssues if re-indexing 1`] = ` | |||
"projectQualifier": "TRK", | |||
"rule": "squid:S4797", | |||
"secondaryLocations": [], | |||
"simpleStatus": "OPEN", | |||
"status": "OPEN", | |||
"tags": [ | |||
"cert", | |||
@@ -95,6 +96,7 @@ exports[`loadIssues should load issues with searchIssues if not re-indexing 1`] | |||
"ruleName": "Handling files is security-sensitive", | |||
"ruleStatus": "READY", | |||
"secondaryLocations": [], | |||
"simpleStatus": "OPEN", | |||
"status": "OPEN", | |||
"tags": [ | |||
"cert", |
@@ -25,14 +25,20 @@ import { | |||
StatusResolvedIcon, | |||
} from 'design-system'; | |||
import * as React from 'react'; | |||
import { IssueSimpleStatus } from '../../types/issues'; | |||
import { Dict } from '../../types/types'; | |||
import { IconProps } from './Icon'; | |||
interface Props extends IconProps { | |||
status: string; | |||
simpleStatus: IssueSimpleStatus; | |||
} | |||
const statusIcons: Dict<(props: IconProps) => React.ReactElement> = { | |||
[IssueSimpleStatus.Accepted]: StatusConfirmedIcon, | |||
[IssueSimpleStatus.Confirmed]: StatusConfirmedIcon, | |||
[IssueSimpleStatus.FalsePositive]: StatusResolvedIcon, | |||
[IssueSimpleStatus.Fixed]: StatusResolvedIcon, | |||
[IssueSimpleStatus.Open]: StatusOpenIcon, | |||
closed: StatusResolvedIcon, | |||
confirm: StatusConfirmedIcon, | |||
confirmed: StatusConfirmedIcon, | |||
@@ -49,8 +55,8 @@ const statusIcons: Dict<(props: IconProps) => React.ReactElement> = { | |||
wontfix: StatusResolvedIcon, | |||
}; | |||
export default function StatusIcon({ status, ...iconProps }: Props) { | |||
const DesiredStatusIcon = statusIcons[status.toLowerCase()]; | |||
export default function SimpleStatusIcon({ simpleStatus, ...iconProps }: Props) { | |||
const DesiredStatusIcon = statusIcons[simpleStatus.toLowerCase()]; | |||
return DesiredStatusIcon ? <DesiredStatusIcon {...iconProps} /> : null; | |||
} |
@@ -32,7 +32,7 @@ import { ComponentPropsType } from '../../../helpers/testUtils'; | |||
import { | |||
IssueActions, | |||
IssueSeverity, | |||
IssueStatus, | |||
IssueSimpleStatus, | |||
IssueTransition, | |||
IssueType, | |||
} from '../../../types/issues'; | |||
@@ -97,7 +97,7 @@ describe('updating', () => { | |||
it('should allow updating the status', async () => { | |||
const { ui } = getPageObject(); | |||
const issue = mockRawIssue(false, { | |||
status: IssueStatus.Open, | |||
simpleStatus: IssueSimpleStatus.Open, | |||
transitions: [IssueTransition.Confirm, IssueTransition.UnConfirm], | |||
}); | |||
issuesHandler.setIssueList([{ issue, snippets: {} }]); | |||
@@ -105,8 +105,8 @@ describe('updating', () => { | |||
issue: mockIssue(false, { ...pick(issue, 'key', 'status', 'transitions') }), | |||
}); | |||
await ui.updateStatus(IssueStatus.Open, IssueTransition.Confirm); | |||
expect(ui.updateStatusBtn(IssueStatus.Confirmed).get()).toBeInTheDocument(); | |||
await ui.updateStatus(IssueSimpleStatus.Open, IssueTransition.Confirm); | |||
expect(ui.updateStatusBtn(IssueSimpleStatus.Confirmed).get()).toBeInTheDocument(); | |||
}); | |||
it('should allow assigning', async () => { | |||
@@ -244,8 +244,8 @@ function getPageObject() { | |||
setSeverityBtn: (severity: IssueSeverity) => byText(`severity.${severity}`), | |||
// Status | |||
updateStatusBtn: (currentStatus: IssueStatus) => | |||
byLabelText(`issue.transition.status_x_click_to_change.issue.status.${currentStatus}`), | |||
updateStatusBtn: (currentStatus: IssueSimpleStatus) => | |||
byLabelText(`issue.transition.status_x_click_to_change.issue.simple_status.${currentStatus}`), | |||
setStatusBtn: (transition: IssueTransition) => byText(`issue.transition.${transition}`), | |||
// Assignee | |||
@@ -297,7 +297,7 @@ function getPageObject() { | |||
await user.click(selectors.setSeverityBtn(newSeverity).get()); | |||
}); | |||
}, | |||
async updateStatus(currentStatus: IssueStatus, transition: IssueTransition) { | |||
async updateStatus(currentStatus: IssueSimpleStatus, transition: IssueTransition) { | |||
await user.click(selectors.updateStatusBtn(currentStatus).get()); | |||
await act(async () => { | |||
await user.click(selectors.setStatusBtn(transition).get()); |
@@ -27,13 +27,13 @@ export const updateIssue = ( | |||
resultPromise: Promise<IssueResponse>, | |||
oldIssue?: Issue, | |||
newIssue?: Issue, | |||
) => { | |||
): Promise<void> => { | |||
const optimisticUpdate = oldIssue !== undefined && newIssue !== undefined; | |||
if (optimisticUpdate) { | |||
onChange(newIssue!); | |||
onChange(newIssue); | |||
} | |||
resultPromise.then( | |||
return resultPromise.then( | |||
(response) => { | |||
if (!optimisticUpdate) { | |||
const issue = parseIssueFromResponse( | |||
@@ -47,7 +47,7 @@ export const updateIssue = ( | |||
}, | |||
(param) => { | |||
if (optimisticUpdate) { | |||
onChange(oldIssue!); | |||
onChange(oldIssue); | |||
} | |||
throwGlobalError(param); | |||
}, |
@@ -19,8 +19,7 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { IssueActions, IssueResolution, IssueType as IssueTypeEnum } from '../../../types/issues'; | |||
import { IssueActions } from '../../../types/issues'; | |||
import { Issue } from '../../../types/types'; | |||
import SoftwareImpactPillList from '../../shared/SoftwareImpactPillList'; | |||
import IssueAssign from './IssueAssign'; | |||
@@ -40,11 +39,6 @@ interface Props { | |||
showSonarLintBadge?: boolean; | |||
} | |||
interface State { | |||
commentAutoTriggered: boolean; | |||
commentPlaceholder: string; | |||
} | |||
export default function IssueActionsBar(props: Props) { | |||
const { | |||
issue, | |||
@@ -56,45 +50,26 @@ export default function IssueActionsBar(props: Props) { | |||
showSonarLintBadge, | |||
} = props; | |||
const [commentState, setCommentState] = React.useState<State>({ | |||
commentAutoTriggered: false, | |||
commentPlaceholder: '', | |||
}); | |||
const [commentPlaceholder, setCommentPlaceholder] = React.useState(''); | |||
const toggleComment = (open: boolean, placeholder = '', autoTriggered = false) => { | |||
setCommentState({ | |||
commentPlaceholder: placeholder, | |||
commentAutoTriggered: autoTriggered, | |||
}); | |||
const toggleComment = (open: boolean, placeholder = '') => { | |||
setCommentPlaceholder(placeholder); | |||
togglePopup('comment', open); | |||
}; | |||
const handleTransition = (issue: Issue) => { | |||
onChange(issue); | |||
if ( | |||
issue.resolution === IssueResolution.FalsePositive || | |||
(issue.resolution === IssueResolution.WontFix && issue.type !== IssueTypeEnum.SecurityHotspot) | |||
) { | |||
toggleComment(true, translate('issue.comment.explain_why'), true); | |||
} | |||
}; | |||
const canAssign = issue.actions.includes(IssueActions.Assign); | |||
const canComment = issue.actions.includes(IssueActions.Comment); | |||
const hasTransitions = issue.transitions.length > 0; | |||
return ( | |||
<div className="sw-flex sw-gap-3"> | |||
<ul className="it__issue-header-actions sw-flex sw-items-center sw-gap-3 sw-body-sm"> | |||
<li> | |||
<li className="sw-relative"> | |||
<IssueTransition | |||
isOpen={currentPopup === 'transition'} | |||
togglePopup={togglePopup} | |||
hasTransitions={hasTransitions} | |||
issue={issue} | |||
onChange={handleTransition} | |||
onChange={onChange} | |||
/> | |||
</li> | |||
@@ -132,8 +107,7 @@ export default function IssueActionsBar(props: Props) { | |||
{canComment && ( | |||
<IssueCommentAction | |||
commentAutoTriggered={commentState.commentAutoTriggered} | |||
commentPlaceholder={commentState.commentPlaceholder} | |||
commentPlaceholder={commentPlaceholder} | |||
currentPopup={currentPopup === 'comment'} | |||
issueKey={issue.key} | |||
onChange={onChange} |
@@ -49,7 +49,7 @@ export default function IssueAssignee(props: Props) { | |||
issue: { assignee, assigneeName, assigneeLogin, assigneeAvatar }, | |||
} = props; | |||
const assinedUser = assigneeName || assignee; | |||
const assinedUser = assigneeName ?? assignee; | |||
const { currentUser } = React.useContext(CurrentUserContext); | |||
const allowCurrentUserSelection = isLoggedIn(currentUser) && currentUser?.login !== assigneeLogin; |
@@ -25,7 +25,6 @@ import { updateIssue } from '../actions'; | |||
import CommentPopup from '../popups/CommentPopup'; | |||
interface Props { | |||
commentAutoTriggered?: boolean; | |||
commentPlaceholder: string; | |||
currentPopup?: boolean; | |||
issueKey: string; | |||
@@ -60,7 +59,6 @@ export default class IssueCommentAction extends React.PureComponent<Props> { | |||
open={!!this.props.currentPopup} | |||
overlay={ | |||
<CommentPopup | |||
autoTriggered={this.props.commentAutoTriggered} | |||
onComment={this.addComment} | |||
placeholder={this.props.commentPlaceholder} | |||
toggleComment={this.props.toggleComment} |
@@ -18,105 +18,90 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { DiscreetSelect } from 'design-system'; | |||
import { Dropdown, PopupPlacement, PopupZLevel, SearchSelectDropdownControl } from 'design-system'; | |||
import * as React from 'react'; | |||
import { GroupBase, OptionProps, components } from 'react-select'; | |||
import { setIssueTransition } from '../../../api/issues'; | |||
import { addIssueComment, setIssueTransition } from '../../../api/issues'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { Issue } from '../../../types/types'; | |||
import { LabelValueSelectOption } from '../../controls/Select'; | |||
import StatusIcon from '../../icons/StatusIcon'; | |||
import StatusHelper from '../../shared/StatusHelper'; | |||
import { updateIssue } from '../actions'; | |||
import { IssueTransitionOverlay } from './IssueTransitionOverlay'; | |||
interface Props { | |||
hasTransitions: boolean; | |||
isOpen: boolean; | |||
issue: Pick<Issue, 'key' | 'resolution' | 'status' | 'transitions' | 'type'>; | |||
issue: Pick<Issue, 'key' | 'resolution' | 'simpleStatus' | 'transitions' | 'type' | 'actions'>; | |||
onChange: (issue: Issue) => void; | |||
togglePopup: (popup: string, show?: boolean) => void; | |||
} | |||
function SingleValueFactory(issue: Props['issue']) { | |||
return function SingleValue< | |||
V, | |||
Option extends LabelValueSelectOption<V>, | |||
IsMulti extends boolean = false, | |||
Group extends GroupBase<Option> = GroupBase<Option>, | |||
>(props: OptionProps<Option, IsMulti, Group>) { | |||
return ( | |||
<components.SingleValue {...props}> | |||
<StatusHelper | |||
className="sw-flex sw-items-center" | |||
resolution={issue.resolution} | |||
status={issue.status} | |||
/> | |||
</components.SingleValue> | |||
); | |||
}; | |||
} | |||
export default class IssueTransition extends React.PureComponent<Props> { | |||
setTransition = ({ value }: { value: string }) => { | |||
updateIssue( | |||
this.props.onChange, | |||
// eslint-disable-next-line local-rules/no-api-imports | |||
setIssueTransition({ issue: this.props.issue.key, transition: value }), | |||
); | |||
this.toggleSetTransition(false); | |||
}; | |||
toggleSetTransition = (open: boolean) => { | |||
this.props.togglePopup('transition', open); | |||
}; | |||
handleClose = () => { | |||
this.toggleSetTransition(false); | |||
}; | |||
export default function IssueTransition(props: Readonly<Props>) { | |||
const { isOpen, issue, onChange, togglePopup } = props; | |||
render() { | |||
const { issue } = this.props; | |||
const [transitioning, setTransitioning] = React.useState(false); | |||
const transitions = issue.transitions.map((transition) => ({ | |||
label: translate('issue.transition', transition), | |||
value: transition, | |||
Icon: <StatusIcon status={transition} />, | |||
})); | |||
async function handleSetTransition(transition: string, comment?: string) { | |||
setTransitioning(true); | |||
if (this.props.hasTransitions) { | |||
return ( | |||
<DiscreetSelect | |||
aria-label={translateWithParameters( | |||
'issue.transition.status_x_click_to_change', | |||
translate('issue.status', issue.status), | |||
)} | |||
size="medium" | |||
className="it__issue-transition" | |||
components={{ SingleValue: SingleValueFactory(issue) }} | |||
menuIsOpen={this.props.isOpen && this.props.hasTransitions} | |||
options={transitions} | |||
setValue={this.setTransition} | |||
onMenuClose={this.handleClose} | |||
onMenuOpen={() => this.toggleSetTransition(true)} | |||
value={issue.resolution ?? 'OPEN'} | |||
customValue={ | |||
<StatusHelper className="sw-flex" resolution={issue.resolution} status={issue.status} /> | |||
} | |||
/> | |||
); | |||
try { | |||
if (typeof comment === 'string' && comment.length > 0) { | |||
await setIssueTransition({ issue: issue.key, transition }); | |||
await updateIssue(onChange, addIssueComment({ issue: issue.key, text: comment })); | |||
} else { | |||
await updateIssue(onChange, setIssueTransition({ issue: issue.key, transition })); | |||
} | |||
togglePopup('transition', false); | |||
} finally { | |||
setTransitioning(false); | |||
} | |||
} | |||
const resolution = issue.resolution && ` (${translate('issue.resolution', issue.resolution)})`; | |||
return ( | |||
<span className="sw-flex sw-items-center sw-gap-1"> | |||
<StatusIcon status={issue.status} /> | |||
function handleClose() { | |||
togglePopup('transition', false); | |||
} | |||
{translate('issue.status', issue.status)} | |||
function onToggleClick() { | |||
togglePopup('transition', !isOpen); | |||
} | |||
{resolution} | |||
</span> | |||
if (issue.transitions?.length) { | |||
return ( | |||
<Dropdown | |||
allowResizing | |||
closeOnClick={false} | |||
id="issue-transition" | |||
onClose={handleClose} | |||
openDropdown={isOpen} | |||
overlay={ | |||
<IssueTransitionOverlay | |||
issue={issue} | |||
onClose={handleClose} | |||
onSetTransition={handleSetTransition} | |||
loading={transitioning} | |||
/> | |||
} | |||
placement={PopupPlacement.Bottom} | |||
zLevel={PopupZLevel.Absolute} | |||
size="medium" | |||
> | |||
{({ a11yAttrs }) => ( | |||
<SearchSelectDropdownControl | |||
{...a11yAttrs} | |||
onClick={onToggleClick} | |||
onClear={handleClose} | |||
isDiscreet | |||
className="it__issue-transition sw-px-1" | |||
label={ | |||
<StatusHelper className="sw-flex sw-items-center" simpleStatus={issue.simpleStatus} /> | |||
} | |||
ariaLabel={translateWithParameters( | |||
'issue.transition.status_x_click_to_change', | |||
translate('issue.simple_status', issue.simpleStatus), | |||
)} | |||
/> | |||
)} | |||
</Dropdown> | |||
); | |||
} | |||
return <StatusHelper simpleStatus={issue.simpleStatus} />; | |||
} |
@@ -0,0 +1,86 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 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 { | |||
HelperHintIcon, | |||
ItemButton, | |||
PageContentFontWrapper, | |||
PopupPlacement, | |||
TextBold, | |||
TextMuted, | |||
Tooltip, | |||
} from 'design-system'; | |||
import * as React from 'react'; | |||
import { useIntl } from 'react-intl'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { IssueTransition } from '../../../types/issues'; | |||
type Props = { | |||
transition: IssueTransition; | |||
selectedTransition?: IssueTransition; | |||
onSelectTransition: (transition: IssueTransition) => void; | |||
}; | |||
export function IssueTransitionItem({ | |||
transition, | |||
selectedTransition, | |||
onSelectTransition, | |||
}: Readonly<Props>) { | |||
const intl = useIntl(); | |||
const tooltips: Record<string, React.ReactFragment> = { | |||
[IssueTransition.Confirm]: ( | |||
<div className="sw-flex sw-flex-col sw-gap-2"> | |||
<span>{translate('issue.transition.confirm.deprecated_tooltip.1')}</span> | |||
<span>{translate('issue.transition.confirm.deprecated_tooltip.2')}</span> | |||
<span>{translate('issue.transition.confirm.deprecated_tooltip.3')}</span> | |||
<span>{translate('issue.transition.confirm.deprecated_tooltip.4')}</span> | |||
</div> | |||
), | |||
[IssueTransition.Resolve]: ( | |||
<div className="sw-flex sw-flex-col sw-gap-2"> | |||
<span>{translate('issue.transition.resolve.deprecated_tooltip.1')}</span> | |||
<span>{translate('issue.transition.resolve.deprecated_tooltip.2')}</span> | |||
<span>{translate('issue.transition.resolve.deprecated_tooltip.3')}</span> | |||
</div> | |||
), | |||
}; | |||
return ( | |||
<ItemButton | |||
key={transition} | |||
onClick={() => onSelectTransition(transition)} | |||
selected={selectedTransition === transition} | |||
className="sw-px-4" | |||
> | |||
<div className="it__issue-transition-option sw-flex sw-flex-col"> | |||
<PageContentFontWrapper className="sw-font-semibold sw-flex sw-gap-1 sw-items-center"> | |||
<TextBold name={intl.formatMessage({ id: `issue.transition.${transition}` })} /> | |||
{tooltips[transition] && ( | |||
<Tooltip overlay={<div>{tooltips[transition]}</div>} placement={PopupPlacement.Right}> | |||
<HelperHintIcon /> | |||
</Tooltip> | |||
)} | |||
</PageContentFontWrapper> | |||
<TextMuted text={translate('issue.transition', transition, 'description')} /> | |||
</div> | |||
</ItemButton> | |||
); | |||
} |
@@ -0,0 +1,136 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 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 { | |||
ButtonPrimary, | |||
ButtonSecondary, | |||
InputTextArea, | |||
ItemDivider, | |||
PageContentFontWrapper, | |||
Spinner, | |||
} from 'design-system'; | |||
import * as React from 'react'; | |||
import { useState } from 'react'; | |||
import { useIntl } from 'react-intl'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { IssueActions, IssueTransition } from '../../../types/issues'; | |||
import { Issue } from '../../../types/types'; | |||
import { isTransitionDeprecated, isTransitionHidden, transitionRequiresComment } from '../helpers'; | |||
import { IssueTransitionItem } from './IssueTransitionItem'; | |||
export type Props = { | |||
issue: Pick<Issue, 'transitions' | 'actions'>; | |||
onClose: () => void; | |||
onSetTransition: (transition: IssueTransition, comment?: string) => void; | |||
loading?: boolean; | |||
}; | |||
export function IssueTransitionOverlay(props: Readonly<Props>) { | |||
const { issue, onClose, onSetTransition, loading } = props; | |||
const intl = useIntl(); | |||
const [comment, setComment] = useState(''); | |||
const [selectedTransition, setSelectedTransition] = useState<IssueTransition>(); | |||
const hasCommentAction = issue.actions.includes(IssueActions.Comment); | |||
function selectTransition(transition: IssueTransition) { | |||
if (!transitionRequiresComment(transition) || !hasCommentAction) { | |||
onSetTransition(transition); | |||
} else { | |||
setSelectedTransition(transition); | |||
} | |||
} | |||
function handleResolve() { | |||
if (selectedTransition) { | |||
onSetTransition(selectedTransition, comment); | |||
} | |||
} | |||
// Filter out hidden transitions and separate deprecated transitions in a different list | |||
const filteredTransitions = issue.transitions.filter( | |||
(transition) => !isTransitionHidden(transition), | |||
); | |||
const filteredTransitionsRecommended = filteredTransitions.filter( | |||
(t) => !isTransitionDeprecated(t), | |||
); | |||
const filteredTransitionsDeprecated = filteredTransitions.filter(isTransitionDeprecated); | |||
return ( | |||
<ul className="sw-flex sw-flex-col"> | |||
{filteredTransitionsRecommended.map((transition) => ( | |||
<IssueTransitionItem | |||
key={transition} | |||
transition={transition} | |||
selectedTransition={selectedTransition} | |||
onSelectTransition={selectTransition} | |||
/> | |||
))} | |||
{filteredTransitionsRecommended.length > 0 && filteredTransitionsDeprecated.length > 0 && ( | |||
<ItemDivider /> | |||
)} | |||
{filteredTransitionsDeprecated.map((transition) => ( | |||
<IssueTransitionItem | |||
key={transition} | |||
transition={transition} | |||
selectedTransition={selectedTransition} | |||
onSelectTransition={selectTransition} | |||
/> | |||
))} | |||
{selectedTransition && ( | |||
<> | |||
<ItemDivider /> | |||
<div className="sw-mx-4 sw-mt-2"> | |||
<PageContentFontWrapper className="sw-font-semibold"> | |||
{intl.formatMessage({ id: 'issue.transition.comment' })} | |||
</PageContentFontWrapper> | |||
<InputTextArea | |||
autoFocus | |||
onChange={(event) => setComment(event.currentTarget.value)} | |||
placeholder={translate( | |||
'issue.transition.comment.placeholder', | |||
selectedTransition ?? '', | |||
)} | |||
rows={5} | |||
value={comment} | |||
size="auto" | |||
className="sw-mt-2 sw-resize-y sw-w-full" | |||
/> | |||
<Spinner loading={loading} className="sw-float-right sw-m-2"> | |||
<div className="sw-mt-2 sw-flex sw-gap-3 sw-justify-end"> | |||
<ButtonPrimary onClick={handleResolve}>{translate('resolve')}</ButtonPrimary> | |||
<ButtonSecondary onClick={onClose}>{translate('cancel')}</ButtonSecondary> | |||
</div> | |||
</Spinner> | |||
</div> | |||
</> | |||
)} | |||
{!selectedTransition && loading && ( | |||
<div className="sw-flex sw-justify-center sw-m-2"> | |||
<Spinner loading className="sw-float-right sw-2" /> | |||
</div> | |||
)} | |||
</ul> | |||
); | |||
} |
@@ -0,0 +1,37 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 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 { IssueTransition } from '../../types/issues'; | |||
export function isTransitionDeprecated(transition: IssueTransition) { | |||
return transition === IssueTransition.Confirm || transition === IssueTransition.Resolve; | |||
} | |||
export function isTransitionHidden(transition: IssueTransition) { | |||
return transition === IssueTransition.WontFix; | |||
} | |||
export function transitionRequiresComment(transition: IssueTransition) { | |||
return [ | |||
IssueTransition.Accept, | |||
IssueTransition.Confirm, | |||
IssueTransition.FalsePositive, | |||
IssueTransition.Resolve, | |||
].includes(transition); | |||
} |
@@ -29,7 +29,6 @@ export interface CommentPopupProps { | |||
toggleComment: (visible: boolean) => void; | |||
placeholder: string; | |||
placement?: PopupPlacement; | |||
autoTriggered?: boolean; | |||
} | |||
export default class CommentPopup extends React.PureComponent<CommentPopupProps> { | |||
@@ -38,7 +37,7 @@ export default class CommentPopup extends React.PureComponent<CommentPopupProps> | |||
}; | |||
render() { | |||
const { comment, autoTriggered } = this.props; | |||
const { comment } = this.props; | |||
return ( | |||
<DropdownOverlay placement={this.props.placement}> | |||
@@ -49,7 +48,6 @@ export default class CommentPopup extends React.PureComponent<CommentPopupProps> | |||
onSaveComment={this.props.onComment} | |||
showFormatHelp | |||
comment={comment?.markdown} | |||
autoTriggered={autoTriggered} | |||
/> | |||
</div> | |||
</DropdownOverlay> |
@@ -18,22 +18,20 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import StatusIcon from '../../components/icons/StatusIcon'; | |||
import { translate } from '../../helpers/l10n'; | |||
import { IssueSimpleStatus } from '../../types/issues'; | |||
import SimpleStatusIcon from '../icons/SimpleStatusIcon'; | |||
interface Props { | |||
className?: string; | |||
resolution: string | undefined; | |||
status: string; | |||
simpleStatus: IssueSimpleStatus; | |||
} | |||
export default function StatusHelper(props: Props) { | |||
const resolution = props.resolution && ` (${translate('issue.resolution', props.resolution)})`; | |||
return ( | |||
<span className={props.className}> | |||
<StatusIcon className="little-spacer-right" status={props.status} /> | |||
{translate('issue.status', props.status)} | |||
{resolution} | |||
<SimpleStatusIcon className="sw-mr-1" simpleStatus={props.simpleStatus} /> | |||
{translate('issue.simple_status', props.simpleStatus)} | |||
</span> | |||
); | |||
} |
@@ -19,7 +19,7 @@ | |||
*/ | |||
import { BugIcon, CodeSmellIcon, SecurityHotspotIcon, VulnerabilityIcon } from 'design-system'; | |||
import { flatten, sortBy } from 'lodash'; | |||
import { IssueType, RawIssue } from '../types/issues'; | |||
import { IssueSimpleStatus, IssueStatus, IssueType, RawIssue } from '../types/issues'; | |||
import { MetricKey } from '../types/metrics'; | |||
import { Dict, Flow, FlowLocation, FlowType, Issue, TextRange } from '../types/types'; | |||
import { UserBase } from '../types/users'; | |||
@@ -160,6 +160,16 @@ export function parseIssueFromResponse( | |||
...splitFlows(issue, components), | |||
...prepareClosed(issue), | |||
...ensureTextRange(issue), | |||
simpleStatus: | |||
issue.simpleStatus ?? | |||
{ | |||
[IssueStatus.Open]: IssueSimpleStatus.Open, | |||
[IssueStatus.Reopened]: IssueSimpleStatus.Open, | |||
[IssueStatus.Closed]: IssueSimpleStatus.Fixed, | |||
[IssueStatus.Resolved]: IssueSimpleStatus.Fixed, | |||
[IssueStatus.Confirmed]: IssueSimpleStatus.Confirmed, | |||
}[issue.status] ?? | |||
IssueSimpleStatus.Open, | |||
} as Issue; | |||
} | |||
@@ -32,7 +32,14 @@ import { | |||
} from '../types/clean-code-taxonomy'; | |||
import { RuleRepository } from '../types/coding-rules'; | |||
import { EditionKey } from '../types/editions'; | |||
import { IssueScope, IssueSeverity, IssueStatus, IssueType, RawIssue } from '../types/issues'; | |||
import { | |||
IssueScope, | |||
IssueSeverity, | |||
IssueSimpleStatus, | |||
IssueStatus, | |||
IssueType, | |||
RawIssue, | |||
} from '../types/issues'; | |||
import { Language } from '../types/languages'; | |||
import { MetricKey, MetricType } from '../types/metrics'; | |||
import { Notification } from '../types/notifications'; | |||
@@ -304,6 +311,7 @@ export function mockRawIssue(withLocations = false, overrides: Partial<RawIssue> | |||
rule: 'javascript:S1067', | |||
severity: IssueSeverity.Major, | |||
status: IssueStatus.Open, | |||
simpleStatus: IssueSimpleStatus.Open, | |||
textRange: { startLine: 25, endLine: 26, startOffset: 0, endOffset: 15 }, | |||
type: IssueType.CodeSmell, | |||
transitions: [], | |||
@@ -358,6 +366,7 @@ export function mockIssue(withLocations = false, overrides: Partial<Issue> = {}) | |||
secondaryLocations: [], | |||
severity: IssueSeverity.Major, | |||
status: IssueStatus.Open, | |||
simpleStatus: IssueSimpleStatus.Open, | |||
textRange: { startLine: 25, endLine: 26, startOffset: 0, endOffset: 15 }, | |||
transitions: [], | |||
type: 'BUG', |
@@ -66,6 +66,14 @@ export enum IssueStatus { | |||
Closed = 'CLOSED', | |||
} | |||
export enum IssueSimpleStatus { | |||
Open = 'OPEN', | |||
Fixed = 'FIXED', | |||
Confirmed = 'CONFIRMED', | |||
Accepted = 'ACCEPTED', | |||
FalsePositive = 'FALSE_POSITIVE', | |||
} | |||
export enum IssueActions { | |||
SetType = 'set_type', | |||
SetTags = 'set_tags', | |||
@@ -75,6 +83,7 @@ export enum IssueActions { | |||
} | |||
export enum IssueTransition { | |||
Accept = 'accept', | |||
Confirm = 'confirm', | |||
UnConfirm = 'unconfirm', | |||
Resolve = 'resolve', | |||
@@ -112,7 +121,7 @@ export interface RawFlowLocation { | |||
export interface RawIssue { | |||
actions: string[]; | |||
transitions: string[]; | |||
transitions: IssueTransition[]; | |||
tags?: string[]; | |||
assignee?: string; | |||
author?: string; | |||
@@ -140,6 +149,7 @@ export interface RawIssue { | |||
message?: string; | |||
severity: string; | |||
status: string; | |||
simpleStatus: IssueSimpleStatus; | |||
textRange?: TextRange; | |||
type: IssueType; | |||
scope: string; |
@@ -25,7 +25,7 @@ import { | |||
SoftwareQuality, | |||
} from './clean-code-taxonomy'; | |||
import { ComponentQualifier, Visibility } from './component'; | |||
import { MessageFormatting } from './issues'; | |||
import { IssueSimpleStatus, IssueTransition, MessageFormatting } from './issues'; | |||
import { NewCodeDefinitionType } from './new-code-definition'; | |||
import { UserActive, UserBase } from './users'; | |||
@@ -291,9 +291,10 @@ export interface Issue { | |||
secondaryLocations: FlowLocation[]; | |||
severity: string; | |||
status: string; | |||
simpleStatus: IssueSimpleStatus; | |||
tags?: string[]; | |||
textRange?: TextRange; | |||
transitions: string[]; | |||
transitions: IssueTransition[]; | |||
type: IssueType; | |||
} | |||
@@ -192,6 +192,7 @@ reset_verb=Reset | |||
reset_to_default=Reset To Default | |||
reset_date=Reset dates | |||
resolution=Resolution | |||
resolve=Resolve | |||
restart=Restart | |||
restore=Restore | |||
result=Result | |||
@@ -923,22 +924,34 @@ issue.severity.severity_x_click_to_change=Severity: {0}, click to change | |||
issue.transition.community_plug_link=SonarSource Community | |||
issue.transition.status_x_click_to_change=Issue status: {0}, click to change | |||
issue.transition=Transition | |||
issue.transition.accept=Accept | |||
issue.transition.accept.description="Won't fix now" | |||
issue.transition.confirm=Confirm | |||
issue.transition.confirm.description=This issue has been reviewed and something should be done eventually to handle it. | |||
issue.transition.unconfirm=Unconfirm | |||
issue.transition.unconfirm.description=This issue should be reviewed again to decide what to do with it. | |||
issue.transition.resolve=Resolve as fixed | |||
issue.transition.resolve.description=This issue has been fixed in the code and is waiting for the next analysis to close it - or reopen it if it was not actually fixed. | |||
issue.transition.falsepositive=Resolve as false positive | |||
issue.transition.falsepositive.description=This issue can be suppressed as it was not raised accurately. Please report false-positives to the {community_plug_link}! | |||
issue.transition.reopen=Reopen | |||
issue.transition.reopen.description=This issue is not resolved, and should be reviewed again. | |||
issue.transition.confirm.description=Deprecated | |||
issue.transition.confirm.deprecated_tooltip.1=The Confirm action is deprecated. | |||
issue.transition.confirm.deprecated_tooltip.2=The next analysis result will show if the issue has been fixed, otherwise it will reopen it automatically. | |||
issue.transition.confirm.deprecated_tooltip.3=If you were using Confirm to communicate with team members, consider assigning the issue or using comments and tags instead. | |||
issue.transition.confirm.deprecated_tooltip.4=If you have reviewed this issue but cannot fix it now, consider marking it as Accepted. | |||
issue.transition.unconfirm=Open | |||
issue.transition.unconfirm.description=Reopen issue | |||
issue.transition.resolve=Fixed | |||
issue.transition.resolve.description=Deprecated | |||
issue.transition.resolve.deprecated_tooltip.1=The Resolve as Fixed action is deprecated. | |||
issue.transition.resolve.deprecated_tooltip.2=The next analysis result will show if the issue has been fixed, otherwise it will reopen the issue automatically. | |||
issue.transition.resolve.deprecated_tooltip.3=If you were using Resolve as Fixed to communicate with team members that an issue is being fixed, consider assigning it or using comments and tags instead. | |||
issue.transition.falsepositive=False Positive | |||
issue.transition.falsepositive.description=Analysis is mistaken | |||
issue.transition.reopen=Open | |||
issue.transition.reopen.description=Reopen issue | |||
issue.transition.comment=Status change comment | |||
issue.transition.comment.placeholder.accept=Share why (optional) | |||
issue.transition.comment.placeholder.confirm=Share why this is confirmed (optional) | |||
issue.transition.comment.placeholder.resolve=Share why this is fixed (optional) | |||
issue.transition.comment.placeholder.falsepositive=Share why this is a false positive (optional) | |||
issue.transition.close=Close | |||
issue.transition.close.description= | |||
issue.transition.wontfix=Resolve as won't fix | |||
issue.transition.wontfix.description=This issue can be suppressed because the rule is irrelevant in this context. | |||
issue.transition.setinreview=Set as In Review | |||
issue.transition.setinreview.description=A review is in progress to check for a vulnerability | |||
issue.transition.wontfix=Won't Fix | |||
issue.transition.wontfix.description=Deprecated | |||
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 | |||
@@ -1040,6 +1053,13 @@ issue.clean_code_attribute.TRUSTWORTHY=Not trustworthy | |||
issue.clean_code_attribute.TRUSTWORTHY.title=This is a responsibility issue, the code is not trustworthy enough. | |||
issue.clean_code_attribute.TRUSTWORTHY.advice=To be trustworthy, the code needs to abstain from revealing or hard-coding private information. | |||
issue.simple_status.OPEN=Open | |||
issue.simple_status.ACCEPTED=Accepted | |||
issue.simple_status.CONFIRMED=Confirmed | |||
issue.simple_status.FIXED=Fixed | |||
issue.simple_status.FALSE_POSITIVE=False Positive | |||
issue.status.ACCEPTED=Accepted | |||
issue.status.REOPENED=Reopened | |||
issue.status.RESOLVED=Resolved | |||
issue.status.OPEN=Open |