aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMathieu Suen <mathieu.suen@sonarsource.com>2022-08-04 17:46:26 +0200
committersonartech <sonartech@sonarsource.com>2022-08-08 20:03:03 +0000
commit69afe4d2004b3b8b683560be9fa4d01f67082ef9 (patch)
tree3d0386e7d2da357122f348dd5320bec09aee04a0
parentef13264ccbe3b9f03ceeaccab2b4366cb126bb24 (diff)
downloadsonarqube-69afe4d2004b3b8b683560be9fa4d01f67082ef9.tar.gz
sonarqube-69afe4d2004b3b8b683560be9fa4d01f67082ef9.zip
SONAR-17147 Refactoring comment popup and improving accessibilty
-rw-r--r--server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts27
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx32
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx26
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx7
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueCommentAction.tsx45
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueTransition.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/IssueActionsBar-test.tsx10
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/IssueCommentAction-test.tsx50
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTags-test.tsx55
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTransition-test.tsx29
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/IssueType-test.tsx14
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueActionsBar-test.tsx.snap3
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueCommentAction-test.tsx.snap81
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTags-test.tsx.snap98
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTransition-test.tsx.snap130
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueType-test.tsx.snap85
-rw-r--r--server/sonar-web/src/main/js/components/issue/popups/CommentForm.tsx88
-rw-r--r--server/sonar-web/src/main/js/components/issue/popups/CommentPopup.tsx94
-rw-r--r--server/sonar-web/src/main/js/components/issue/popups/CommentTile.tsx61
-rw-r--r--server/sonar-web/src/main/js/components/issue/popups/CommentsListPopup.tsx78
-rw-r--r--server/sonar-web/src/main/js/components/issue/popups/__tests__/CommentPopup-test.tsx66
-rw-r--r--server/sonar-web/src/main/js/components/issue/popups/__tests__/SetIssueTagsPopup-test.tsx28
-rw-r--r--server/sonar-web/src/main/js/components/issue/popups/__tests__/SetSeverityPopup-test.tsx27
-rw-r--r--server/sonar-web/src/main/js/components/issue/popups/__tests__/SetTransitionPopup-test.tsx54
-rw-r--r--server/sonar-web/src/main/js/components/issue/popups/__tests__/SetTypePopup-test.tsx27
-rw-r--r--server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/CommentPopup-test.tsx.snap95
-rw-r--r--server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetIssueTagsPopup-test.tsx.snap25
-rw-r--r--server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetSeverityPopup-test.tsx.snap75
-rw-r--r--server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetTransitionPopup-test.tsx.snap157
-rw-r--r--server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetTypePopup-test.tsx.snap51
-rw-r--r--server/sonar-web/src/main/js/types/issues.ts1
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties2
32 files changed, 309 insertions, 1314 deletions
diff --git a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
index f40a56052a6..7236a4c7272 100644
--- a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
+++ b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
@@ -88,6 +88,7 @@ export default class IssuesServiceMock {
list: IssueData[];
constructor() {
+ // Comment should have their own store as we can test better CRUD operation
this.sourceViewerFiles = [
mockSourceViewerFile('file.foo', 'project'),
mockSourceViewerFile('file.bar', 'project')
@@ -383,9 +384,30 @@ export default class IssuesServiceMock {
const statusMap: { [key: string]: string } = {
confirm: 'CONFIRMED',
unconfirm: 'REOPENED',
- resolve: 'RESOLVED'
+ resolve: 'RESOLVED',
+ wontfix: 'RESOLVED',
+ falsepositive: 'RESOLVED'
};
- return this.getActionsResponse({ status: statusMap[data.transition] }, data.issue);
+ const transitionMap: Dict<string[]> = {
+ REOPENED: ['confirm', 'resolve', 'falsepositive', 'wontfix'],
+ OPEN: ['confirm', 'resolve', 'falsepositive', 'wontfix'],
+ CONFIRMED: ['resolve', 'unconfirm', 'falsepositive', 'wontfix'],
+ RESOLVED: ['reopen']
+ };
+
+ const resolutionMap: Dict<string> = {
+ wontfix: 'WONTFIX',
+ falsepositive: 'FALSE-POSITIVE'
+ };
+
+ return this.getActionsResponse(
+ {
+ status: statusMap[data.transition],
+ transitions: transitionMap[statusMap[data.transition]],
+ resolution: resolutionMap[data.transition]
+ },
+ data.issue
+ );
};
handleSetIssueTags = (data: { issue: string; tags: string }) => {
@@ -456,6 +478,7 @@ export default class IssuesServiceMock {
...issueDataSelected?.issue,
...overrides
};
+
return this.reply({
issue: issueDataSelected.issue
});
diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
index 12eba71e38f..4514bf73532 100644
--- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
@@ -232,7 +232,15 @@ it('should be able to perform action on issues', async () => {
name: `issue.transition.status_x_click_to_change.issue.status.CONFIRMED`
})
).toBeInTheDocument();
- await user.keyboard('{Escape}');
+
+ // As won't fix
+ await user.click(screen.getByText('issue.status.CONFIRMED'));
+ await user.click(screen.getByText('issue.transition.wontfix'));
+ // Comment should open and close
+ expect(screen.getByRole('button', { name: 'issue.comment.submit' })).toBeInTheDocument();
+ await user.keyboard('test');
+ await user.click(screen.getByRole('button', { name: 'issue.comment.submit' }));
+ expect(screen.queryByRole('button', { name: 'issue.comment.submit' })).not.toBeInTheDocument();
// assigning issue to a different user
expect(
@@ -271,6 +279,13 @@ it('should be able to perform action on issues', async () => {
await user.click(screen.getByText('issue.comment.submit'));
expect(screen.getByText('comment')).toBeInTheDocument();
+ // Cancel editing the comment
+ expect(screen.getByRole('button', { name: 'issue.comment.edit' })).toBeInTheDocument();
+ await user.click(screen.getByRole('button', { name: 'issue.comment.edit' }));
+ await user.keyboard('New ');
+ await user.click(screen.getByRole('button', { name: 'issue.comment.edit.cancel' }));
+ expect(screen.queryByText('New comment')).not.toBeInTheDocument();
+
// editing the comment
expect(screen.getByRole('button', { name: 'issue.comment.edit' })).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: 'issue.comment.edit' }));
@@ -303,7 +318,12 @@ it('should be able to perform action on issues', async () => {
expect(screen.getByText('accessibility')).toBeInTheDocument();
await user.click(screen.getByText('accessibility'));
- expect(screen.getAllByText('accessibility')).toHaveLength(2); // one in the list of selector and one selected
+ await user.click(screen.getByText('android'));
+ expect(screen.getByTitle('accessibility, android')).toBeInTheDocument();
+
+ // Unslect
+ await user.click(screen.getByText('accessibility'));
+ expect(screen.getByTitle('android')).toBeInTheDocument();
await user.click(screen.getByRole('searchbox', { name: 'search_verb' }));
await user.keyboard('addNewTag');
@@ -327,11 +347,13 @@ it('should not allow performing actions when user does not have permission', asy
name: `issue.type.type_x_click_to_change.issue.type.CODE_SMELL`
})
).not.toBeInTheDocument();
- expect(
- screen.queryByRole('button', {
+
+ await user.click(
+ screen.getByRole('button', {
name: `issue.comment.add_comment`
})
- ).not.toBeInTheDocument();
+ );
+ expect(screen.queryByRole('button', { name: 'issue.comment.submit' })).not.toBeInTheDocument();
expect(
screen.queryByRole('button', {
name: `issue.transition.status_x_click_to_change.issue.status.OPEN`
diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx
index d2a6c9709d0..0dc77673ba1 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx
@@ -20,7 +20,7 @@
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
-import { deleteIssueComment, editIssueComment, setIssueAssignee } from '../../../api/issues';
+import { setIssueAssignee } from '../../../api/issues';
import DocumentationTooltip from '../../../components/common/DocumentationTooltip';
import Tooltip from '../../../components/controls/Tooltip';
import LinkIcon from '../../../components/icons/LinkIcon';
@@ -65,17 +65,21 @@ export default class IssueHeader extends React.PureComponent<Props, State> {
document.removeEventListener('keydown', this.handleKeyDown, { capture: true });
}
- handleIssuePopupToggle = (popupName: string, open = true) => {
- const name = open ? popupName : undefined;
- this.setState({ issuePopupName: name });
- };
+ handleIssuePopupToggle = (popupName: string, open?: boolean) => {
+ const openPopupState = { issuePopupName: popupName };
- deleteComment = (comment: string) => {
- updateIssue(this.props.onIssueChange, deleteIssueComment({ comment }));
- };
+ const closePopupState = { issuePopupName: undefined };
- editComment = (comment: string, text: string) => {
- updateIssue(this.props.onIssueChange, editIssueComment({ comment, text }));
+ this.setState(({ issuePopupName }) => {
+ if (open) {
+ return openPopupState;
+ } else if (open === false) {
+ return closePopupState;
+ }
+
+ // toggle popup
+ return issuePopupName === popupName ? closePopupState : openPopupState;
+ });
};
handleAssignement = (login: string) => {
@@ -229,8 +233,6 @@ export default class IssueHeader extends React.PureComponent<Props, State> {
onAssign={this.handleAssignement}
onChange={this.props.onIssueChange}
togglePopup={this.handleIssuePopupToggle}
- deleteComment={this.deleteComment}
- onEdit={this.editComment}
showCommentsInPopup={true}
/>
</>
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx
index b144ef317c6..3a59bc53f32 100644
--- a/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx
+++ b/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx
@@ -36,8 +36,6 @@ interface Props {
onAssign: (login: string) => void;
onChange: (issue: Issue) => void;
togglePopup: (popup: string, show?: boolean) => void;
- deleteComment?: (comment: string) => void;
- onEdit?: (comment: string, text: string) => void;
className?: string;
showCommentsInPopup?: boolean;
}
@@ -148,7 +146,7 @@ export default class IssueActionsBar extends React.PureComponent<Props, State> {
</span>
</div>
)}
- {canComment && (
+ {(canComment || showCommentsInPopup) && (
<IssueCommentAction
commentAutoTriggered={this.state.commentAutoTriggered}
commentPlaceholder={this.state.commentPlaceholder}
@@ -157,8 +155,7 @@ export default class IssueActionsBar extends React.PureComponent<Props, State> {
onChange={this.props.onChange}
toggleComment={this.toggleComment}
comments={issue.comments}
- deleteComment={this.props.deleteComment}
- onEdit={this.props.onEdit}
+ canComment={canComment}
showCommentsInPopup={showCommentsInPopup}
/>
)}
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueCommentAction.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueCommentAction.tsx
index 43332b92dae..7a234bc6fa4 100644
--- a/server/sonar-web/src/main/js/components/issue/components/IssueCommentAction.tsx
+++ b/server/sonar-web/src/main/js/components/issue/components/IssueCommentAction.tsx
@@ -18,23 +18,23 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { addIssueComment } from '../../../api/issues';
+import { addIssueComment, deleteIssueComment, editIssueComment } from '../../../api/issues';
import { ButtonLink } from '../../../components/controls/buttons';
import Toggler from '../../../components/controls/Toggler';
import { translate } from '../../../helpers/l10n';
import { Issue, IssueComment } from '../../../types/types';
import { updateIssue } from '../actions';
import CommentPopup from '../popups/CommentPopup';
+import CommentListPopup from '../popups/CommentsListPopup';
interface Props {
+ canComment: boolean;
commentAutoTriggered?: boolean;
commentPlaceholder: string;
currentPopup?: string;
issueKey: string;
onChange: (issue: Issue) => void;
toggleComment: (open?: boolean, placeholder?: string, autoTriggered?: boolean) => void;
- deleteComment?: (comment: string) => void;
- onEdit?: (comment: string, text: string) => void;
comments?: IssueComment[];
showCommentsInPopup?: boolean;
}
@@ -48,6 +48,14 @@ export default class IssueCommentAction extends React.PureComponent<Props> {
}
};
+ handleEditComment = (comment: string, text: string) => {
+ updateIssue(this.props.onChange, editIssueComment({ comment, text }));
+ };
+
+ handleDeleteComment = (comment: string) => {
+ updateIssue(this.props.onChange, deleteIssueComment({ comment }));
+ };
+
handleCommentClick = () => {
this.props.toggleComment();
};
@@ -57,7 +65,7 @@ export default class IssueCommentAction extends React.PureComponent<Props> {
};
render() {
- const { comments, showCommentsInPopup } = this.props;
+ const { comments, showCommentsInPopup, canComment } = this.props;
return (
<div className="issue-meta dropdown">
<Toggler
@@ -65,16 +73,25 @@ export default class IssueCommentAction extends React.PureComponent<Props> {
onRequestClose={this.handleClose}
open={this.props.currentPopup === 'comment'}
overlay={
- <CommentPopup
- autoTriggered={this.props.commentAutoTriggered}
- onComment={this.addComment}
- placeholder={this.props.commentPlaceholder}
- toggleComment={this.props.toggleComment}
- comments={comments}
- deleteComment={this.props.deleteComment}
- onEdit={this.props.onEdit}
- showCommentsInPopup={showCommentsInPopup}
- />
+ showCommentsInPopup ? (
+ <CommentListPopup
+ comments={comments}
+ deleteComment={this.handleDeleteComment}
+ onAddComment={this.addComment}
+ onEdit={this.handleEditComment}
+ placeholder={this.props.commentPlaceholder}
+ toggleComment={this.props.toggleComment}
+ autoTriggered={this.props.commentAutoTriggered}
+ canComment={canComment}
+ />
+ ) : (
+ <CommentPopup
+ autoTriggered={this.props.commentAutoTriggered}
+ onComment={this.addComment}
+ placeholder={this.props.commentPlaceholder}
+ toggleComment={this.props.toggleComment}
+ />
+ )
}>
<ButtonLink
aria-expanded={this.props.currentPopup === 'comment'}
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueTransition.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueTransition.tsx
index 7a4fe3515dc..e3bc90d7681 100644
--- a/server/sonar-web/src/main/js/components/issue/components/IssueTransition.tsx
+++ b/server/sonar-web/src/main/js/components/issue/components/IssueTransition.tsx
@@ -42,7 +42,7 @@ export default class IssueTransition extends React.PureComponent<Props> {
this.props.onChange,
setIssueTransition({ issue: this.props.issue.key, transition })
);
- this.toggleSetTransition();
+ this.toggleSetTransition(false);
};
toggleSetTransition = (open?: boolean) => {
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueActionsBar-test.tsx b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueActionsBar-test.tsx
index 84cd9848c54..00655ccdaa2 100644
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueActionsBar-test.tsx
+++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueActionsBar-test.tsx
@@ -32,8 +32,6 @@ it('should render issue correctly', () => {
onAssign={jest.fn()}
onChange={jest.fn()}
togglePopup={jest.fn()}
- deleteComment={jest.fn()}
- onEdit={jest.fn()}
/>
);
expect(element).toMatchSnapshot();
@@ -46,8 +44,6 @@ it('should render security hotspot correctly', () => {
onAssign={jest.fn()}
onChange={jest.fn()}
togglePopup={jest.fn()}
- deleteComment={jest.fn()}
- onEdit={jest.fn()}
/>
);
expect(element).toMatchSnapshot();
@@ -60,8 +56,6 @@ it('should render commentable correctly', () => {
onAssign={jest.fn()}
onChange={jest.fn()}
togglePopup={jest.fn()}
- deleteComment={jest.fn()}
- onEdit={jest.fn()}
/>
);
expect(element).toMatchSnapshot();
@@ -74,8 +68,6 @@ it('should render effort correctly', () => {
onAssign={jest.fn()}
onChange={jest.fn()}
togglePopup={jest.fn()}
- deleteComment={jest.fn()}
- onEdit={jest.fn()}
/>
);
expect(element).toMatchSnapshot();
@@ -92,8 +84,6 @@ describe('callback', () => {
onAssign={jest.fn()}
onChange={onChangeMock}
togglePopup={togglePopupMock}
- deleteComment={jest.fn()}
- onEdit={jest.fn()}
/>
);
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueCommentAction-test.tsx b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueCommentAction-test.tsx
deleted file mode 100644
index cafec1f9c41..00000000000
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueCommentAction-test.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { click } from '../../../../helpers/testUtils';
-import IssueCommentAction from '../IssueCommentAction';
-
-it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot();
-});
-
-it('should open the popup when the button is clicked', () => {
- const toggleComment = jest.fn();
- const element = shallowRender({ toggleComment });
- click(element.find('ButtonLink'));
- expect(toggleComment.mock.calls.length).toBe(1);
- element.setProps({ currentPopup: 'comment' });
- expect(element).toMatchSnapshot();
-});
-
-function shallowRender(props: Partial<IssueCommentAction['props']> = {}) {
- return shallow(
- <IssueCommentAction
- commentPlaceholder=""
- issueKey="issue-key"
- onChange={jest.fn()}
- toggleComment={jest.fn()}
- deleteComment={jest.fn()}
- onEdit={jest.fn()}
- {...props}
- />
- );
-}
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTags-test.tsx b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTags-test.tsx
deleted file mode 100644
index 1502fc53721..00000000000
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTags-test.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { click } from '../../../../helpers/testUtils';
-import IssueTags from '../IssueTags';
-
-const issue = { key: 'issuekey', tags: ['mytag', 'test'] };
-
-it('should render without the action when the correct rights are missing', () => {
- expect(shallowRender({ canSetTags: false, issue: { ...issue, tags: [] } })).toMatchSnapshot();
-});
-
-it('should render with the action', () => {
- expect(shallowRender()).toMatchSnapshot();
-});
-
-it('should open the popup when the button is clicked', () => {
- const togglePopup = jest.fn();
- const element = shallowRender({ togglePopup });
- click(element.find('ButtonLink'));
- expect(togglePopup.mock.calls).toMatchSnapshot();
- element.setProps({ isOpen: true });
- expect(element).toMatchSnapshot();
-});
-
-function shallowRender(props: Partial<IssueTags['props']> = {}) {
- return shallow(
- <IssueTags
- canSetTags={true}
- isOpen={false}
- issue={issue}
- onChange={jest.fn()}
- togglePopup={jest.fn()}
- {...props}
- />
- );
-}
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTransition-test.tsx b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTransition-test.tsx
index bbb635ac90c..a0985d8ef70 100644
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTransition-test.tsx
+++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTransition-test.tsx
@@ -19,7 +19,6 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { click } from '../../../../helpers/testUtils';
import IssueTransition from '../IssueTransition';
const issue: IssueTransition['props']['issue'] = {
@@ -39,34 +38,6 @@ it('should render without the action when there is no transitions', () => {
).toMatchSnapshot();
});
-it('should render with the action', () => {
- expect(shallowRender()).toMatchSnapshot();
-});
-
-it('should render with a resolution', () => {
- expect(
- shallowRender({
- issue: {
- fromHotspot: false,
- key: 'foo1234',
- transitions: ['reopen'],
- status: 'RESOLVED',
- resolution: 'FIXED',
- type: 'BUG'
- }
- })
- ).toMatchSnapshot();
-});
-
-it('should open the popup when the button is clicked', () => {
- const togglePopup = jest.fn();
- const element = shallowRender({ togglePopup });
- click(element.find('ButtonLink'));
- expect(togglePopup.mock.calls).toMatchSnapshot();
- element.setProps({ isOpen: true });
- expect(element).toMatchSnapshot();
-});
-
function shallowRender(props: Partial<IssueTransition['props']> = {}) {
return shallow(
<IssueTransition
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueType-test.tsx b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueType-test.tsx
index 7242637a05a..ef627eeec5d 100644
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueType-test.tsx
+++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueType-test.tsx
@@ -19,7 +19,6 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { click } from '../../../../helpers/testUtils';
import { Issue } from '../../../../types/types';
import IssueType from '../IssueType';
@@ -29,19 +28,6 @@ it('should render without the action when the correct rights are missing', () =>
expect(shallowRender({ canSetType: false })).toMatchSnapshot();
});
-it('should render with the action', () => {
- expect(shallowRender()).toMatchSnapshot();
-});
-
-it('should open the popup when the button is clicked', () => {
- const togglePopup = jest.fn();
- const element = shallowRender({ togglePopup });
- click(element.find('ButtonLink'));
- expect(togglePopup.mock.calls).toMatchSnapshot();
- element.setProps({ isOpen: true });
- expect(element).toMatchSnapshot();
-});
-
function shallowRender(props: Partial<IssueType['props']> = {}) {
return shallow(
<IssueType
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueActionsBar-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueActionsBar-test.tsx.snap
index 5bf3b691fb5..2efed8970bf 100644
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueActionsBar-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueActionsBar-test.tsx.snap
@@ -180,12 +180,11 @@ exports[`should render commentable correctly 1`] = `
/>
</div>
<IssueCommentAction
+ canComment={true}
commentAutoTriggered={false}
commentPlaceholder=""
- deleteComment={[MockFunction]}
issueKey="AVsae-CQS-9G3txfbFN2"
onChange={[MockFunction]}
- onEdit={[MockFunction]}
toggleComment={[Function]}
/>
</div>
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueCommentAction-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueCommentAction-test.tsx.snap
deleted file mode 100644
index 602e0060008..00000000000
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueCommentAction-test.tsx.snap
+++ /dev/null
@@ -1,81 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should open the popup when the button is clicked 1`] = `
-<div
- className="issue-meta dropdown"
->
- <Toggler
- closeOnClickOutside={false}
- onRequestClose={[Function]}
- open={true}
- overlay={
- <CommentPopup
- deleteComment={[MockFunction]}
- onComment={[Function]}
- onEdit={[MockFunction]}
- placeholder=""
- toggleComment={
- [MockFunction] {
- "calls": Array [
- Array [],
- ],
- "results": Array [
- Object {
- "type": "return",
- "value": undefined,
- },
- ],
- }
- }
- />
- }
- >
- <ButtonLink
- aria-expanded={true}
- aria-label="issue.comment.add_comment"
- className="issue-action js-issue-comment"
- onClick={[Function]}
- >
- <span
- className="issue-meta-label"
- >
- issue.comment.formlink
- </span>
- </ButtonLink>
- </Toggler>
-</div>
-`;
-
-exports[`should render correctly 1`] = `
-<div
- className="issue-meta dropdown"
->
- <Toggler
- closeOnClickOutside={false}
- onRequestClose={[Function]}
- open={false}
- overlay={
- <CommentPopup
- deleteComment={[MockFunction]}
- onComment={[Function]}
- onEdit={[MockFunction]}
- placeholder=""
- toggleComment={[MockFunction]}
- />
- }
- >
- <ButtonLink
- aria-expanded={false}
- aria-label="issue.comment.add_comment"
- className="issue-action js-issue-comment"
- onClick={[Function]}
- >
- <span
- className="issue-meta-label"
- >
- issue.comment.formlink
- </span>
- </ButtonLink>
- </Toggler>
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTags-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTags-test.tsx.snap
deleted file mode 100644
index 106169cb019..00000000000
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTags-test.tsx.snap
+++ /dev/null
@@ -1,98 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should open the popup when the button is clicked 1`] = `
-Array [
- Array [
- "edit-tags",
- undefined,
- ],
-]
-`;
-
-exports[`should open the popup when the button is clicked 2`] = `
-<div
- className="dropdown"
->
- <Toggler
- onRequestClose={[Function]}
- open={true}
- overlay={
- <SetIssueTagsPopup
- selectedTags={
- Array [
- "mytag",
- "test",
- ]
- }
- setTags={[Function]}
- />
- }
- >
- <ButtonLink
- aria-expanded={true}
- className="issue-action issue-action-with-options js-issue-edit-tags"
- onClick={[Function]}
- >
- <TagsList
- allowUpdate={true}
- tags={
- Array [
- "mytag",
- "test",
- ]
- }
- />
- </ButtonLink>
- </Toggler>
-</div>
-`;
-
-exports[`should render with the action 1`] = `
-<div
- className="dropdown"
->
- <Toggler
- onRequestClose={[Function]}
- open={false}
- overlay={
- <SetIssueTagsPopup
- selectedTags={
- Array [
- "mytag",
- "test",
- ]
- }
- setTags={[Function]}
- />
- }
- >
- <ButtonLink
- aria-expanded={false}
- className="issue-action issue-action-with-options js-issue-edit-tags"
- onClick={[Function]}
- >
- <TagsList
- allowUpdate={true}
- tags={
- Array [
- "mytag",
- "test",
- ]
- }
- />
- </ButtonLink>
- </Toggler>
-</div>
-`;
-
-exports[`should render without the action when the correct rights are missing 1`] = `
-<TagsList
- allowUpdate={false}
- className="note"
- tags={
- Array [
- "issue.no_tag",
- ]
- }
-/>
-`;
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTransition-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTransition-test.tsx.snap
index d3d54e58071..e4eb999534e 100644
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTransition-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTransition-test.tsx.snap
@@ -1,135 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should open the popup when the button is clicked 1`] = `
-Array [
- Array [
- "transition",
- undefined,
- ],
-]
-`;
-
-exports[`should open the popup when the button is clicked 2`] = `
-<div
- className="dropdown"
->
- <Toggler
- onRequestClose={[Function]}
- open={true}
- overlay={
- <SetTransitionPopup
- fromHotspot={false}
- onSelect={[Function]}
- transitions={
- Array [
- "confirm",
- "resolve",
- "falsepositive",
- "wontfix",
- ]
- }
- type="BUG"
- />
- }
- >
- <ButtonLink
- aria-expanded={true}
- aria-label="issue.transition.status_x_click_to_change.issue.status.OPEN"
- className="issue-action issue-action-with-options js-issue-transition"
- onClick={[Function]}
- >
- <StatusHelper
- className="issue-meta-label"
- status="OPEN"
- />
- <DropdownIcon
- className="little-spacer-left"
- />
- </ButtonLink>
- </Toggler>
-</div>
-`;
-
-exports[`should render with a resolution 1`] = `
-<div
- className="dropdown"
->
- <Toggler
- onRequestClose={[Function]}
- open={false}
- overlay={
- <SetTransitionPopup
- fromHotspot={false}
- onSelect={[Function]}
- transitions={
- Array [
- "reopen",
- ]
- }
- type="BUG"
- />
- }
- >
- <ButtonLink
- aria-expanded={false}
- aria-label="issue.transition.status_x_click_to_change.issue.status.RESOLVED"
- className="issue-action issue-action-with-options js-issue-transition"
- onClick={[Function]}
- >
- <StatusHelper
- className="issue-meta-label"
- resolution="FIXED"
- status="RESOLVED"
- />
- <DropdownIcon
- className="little-spacer-left"
- />
- </ButtonLink>
- </Toggler>
-</div>
-`;
-
-exports[`should render with the action 1`] = `
-<div
- className="dropdown"
->
- <Toggler
- onRequestClose={[Function]}
- open={false}
- overlay={
- <SetTransitionPopup
- fromHotspot={false}
- onSelect={[Function]}
- transitions={
- Array [
- "confirm",
- "resolve",
- "falsepositive",
- "wontfix",
- ]
- }
- type="BUG"
- />
- }
- >
- <ButtonLink
- aria-expanded={false}
- aria-label="issue.transition.status_x_click_to_change.issue.status.OPEN"
- className="issue-action issue-action-with-options js-issue-transition"
- onClick={[Function]}
- >
- <StatusHelper
- className="issue-meta-label"
- status="OPEN"
- />
- <DropdownIcon
- className="little-spacer-left"
- />
- </ButtonLink>
- </Toggler>
-</div>
-`;
-
exports[`should render without the action when there is no transitions 1`] = `
<StatusHelper
className="issue-meta-label"
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueType-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueType-test.tsx.snap
index b07ade8d1fd..1078e100a75 100644
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueType-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueType-test.tsx.snap
@@ -1,90 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should open the popup when the button is clicked 1`] = `
-Array [
- Array [
- "set-type",
- undefined,
- ],
-]
-`;
-
-exports[`should open the popup when the button is clicked 2`] = `
-<div
- className="dropdown"
->
- <Toggler
- onRequestClose={[Function]}
- open={true}
- overlay={
- <SetTypePopup
- issue={
- Object {
- "type": "BUG",
- }
- }
- onSelect={[Function]}
- />
- }
- >
- <ButtonLink
- aria-expanded={true}
- aria-label="issue.type.type_x_click_to_change.issue.type.BUG"
- className="issue-action issue-action-with-options js-issue-set-type"
- onClick={[Function]}
- >
- <IssueTypeIcon
- className="little-spacer-right"
- fill="#333"
- query="BUG"
- />
- issue.type.BUG
- <DropdownIcon
- className="little-spacer-left"
- />
- </ButtonLink>
- </Toggler>
-</div>
-`;
-
-exports[`should render with the action 1`] = `
-<div
- className="dropdown"
->
- <Toggler
- onRequestClose={[Function]}
- open={false}
- overlay={
- <SetTypePopup
- issue={
- Object {
- "type": "BUG",
- }
- }
- onSelect={[Function]}
- />
- }
- >
- <ButtonLink
- aria-expanded={false}
- aria-label="issue.type.type_x_click_to_change.issue.type.BUG"
- className="issue-action issue-action-with-options js-issue-set-type"
- onClick={[Function]}
- >
- <IssueTypeIcon
- className="little-spacer-right"
- fill="#333"
- query="BUG"
- />
- issue.type.BUG
- <DropdownIcon
- className="little-spacer-left"
- />
- </ButtonLink>
- </Toggler>
-</div>
-`;
-
exports[`should render without the action when the correct rights are missing 1`] = `
<span>
<IssueTypeIcon
diff --git a/server/sonar-web/src/main/js/components/issue/popups/CommentForm.tsx b/server/sonar-web/src/main/js/components/issue/popups/CommentForm.tsx
new file mode 100644
index 00000000000..4fd1f3fa6b1
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/issue/popups/CommentForm.tsx
@@ -0,0 +1,88 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { KeyboardKeys } from '../../../helpers/keycodes';
+import { translate } from '../../../helpers/l10n';
+import FormattingTips from '../../common/FormattingTips';
+import { Button, ResetButtonLink } from '../../controls/buttons';
+
+export interface CommentFormProps {
+ comment?: string;
+ onCancel: () => void;
+ onSaveComment: (comment: string) => void;
+ placeholder?: string;
+ showFormatHelp: boolean;
+ autoTriggered?: boolean;
+}
+
+export default function CommentForm(props: CommentFormProps) {
+ const { comment, placeholder, showFormatHelp, autoTriggered } = props;
+ const [editComment, setEditComment] = React.useState(comment || '');
+
+ return (
+ <>
+ <div className="issue-comment-form-text">
+ <textarea
+ autoFocus={true}
+ placeholder={placeholder}
+ onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) =>
+ setEditComment(event.target.value)
+ }
+ onKeyDown={(event: React.KeyboardEvent) => {
+ if (event.nativeEvent.key === KeyboardKeys.Enter && (event.metaKey || event.ctrlKey)) {
+ props.onSaveComment(editComment);
+ setEditComment('');
+ }
+ }}
+ rows={2}
+ value={editComment}
+ />
+ </div>
+ <div className="issue-comment-form-footer">
+ {showFormatHelp && (
+ <div className="issue-comment-form-tips">
+ <FormattingTips />
+ </div>
+ )}
+ <div className="issue-comment-form-actions">
+ <Button
+ className="js-issue-comment-submit little-spacer-right"
+ disabled={editComment.trim().length < 1}
+ onClick={() => {
+ props.onSaveComment(editComment);
+ setEditComment('');
+ }}>
+ {comment ? translate('save') : translate('issue.comment.submit')}
+ </Button>
+ <ResetButtonLink
+ className="js-issue-comment-cancel"
+ aria-label={
+ comment
+ ? translate('issue.comment.edit.cancel')
+ : translate('issue.comment.add_comment.cancel')
+ }
+ onClick={() => props.onCancel()}>
+ {autoTriggered ? translate('skip') : translate('cancel')}
+ </ResetButtonLink>
+ </div>
+ </div>
+ </>
+ );
+}
diff --git a/server/sonar-web/src/main/js/components/issue/popups/CommentPopup.tsx b/server/sonar-web/src/main/js/components/issue/popups/CommentPopup.tsx
index e5fcc5f7411..9e122c0d6d7 100644
--- a/server/sonar-web/src/main/js/components/issue/popups/CommentPopup.tsx
+++ b/server/sonar-web/src/main/js/components/issue/popups/CommentPopup.tsx
@@ -18,111 +18,39 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Button, ResetButtonLink } from '../../../components/controls/buttons';
import { DropdownOverlay } from '../../../components/controls/Dropdown';
import { PopupPlacement } from '../../../components/ui/popups';
-import { KeyboardKeys } from '../../../helpers/keycodes';
-import { translate } from '../../../helpers/l10n';
import { IssueComment } from '../../../types/types';
-import FormattingTips from '../../common/FormattingTips';
-import CommentList from './CommentList';
+import CommentForm from './CommentForm';
export interface CommentPopupProps {
comment?: Pick<IssueComment, 'markdown'>;
onComment: (text: string) => void;
toggleComment: (visible: boolean) => void;
- deleteComment?: (comment: string) => void;
- onEdit?: (comment: string, text: string) => void;
placeholder: string;
placement?: PopupPlacement;
autoTriggered?: boolean;
- comments?: IssueComment[];
- showCommentsInPopup?: boolean;
}
-interface State {
- textComment: string;
-}
-
-export default class CommentPopup extends React.PureComponent<CommentPopupProps, State> {
- constructor(props: CommentPopupProps) {
- super(props);
- this.state = {
- textComment: props.comment ? props.comment.markdown : ''
- };
- }
-
- handleCommentChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
- this.setState({ textComment: event.target.value });
- };
-
- handleCommentClick = () => {
- if (this.state.textComment.trim().length > 0) {
- this.props.onComment(this.state.textComment);
- this.setState({ textComment: '' });
- }
- };
-
+export default class CommentPopup extends React.PureComponent<CommentPopupProps> {
handleCancelClick = () => {
this.props.toggleComment(false);
};
- handleKeyboard = (event: React.KeyboardEvent) => {
- if (event.nativeEvent.key === KeyboardKeys.Enter && (event.metaKey || event.ctrlKey)) {
- this.handleCommentClick();
- } else if (
- [
- KeyboardKeys.UpArrow,
- KeyboardKeys.DownArrow,
- KeyboardKeys.LeftArrow,
- KeyboardKeys.RightArrow
- ].includes(event.nativeEvent.key as KeyboardKeys)
- ) {
- // Arrow keys
- event.stopPropagation();
- }
- };
-
render() {
- const { comment, autoTriggered, comments, showCommentsInPopup } = this.props;
+ const { comment, autoTriggered } = this.props;
return (
<DropdownOverlay placement={this.props.placement}>
<div className="issue-comment-bubble-popup">
- {showCommentsInPopup && this.props.deleteComment && this.props.onEdit && (
- <CommentList
- comments={comments}
- deleteComment={this.props.deleteComment}
- onEdit={this.props.onEdit}
- />
- )}
- <div className="issue-comment-form-text">
- <textarea
- autoFocus={true}
- onChange={this.handleCommentChange}
- onKeyDown={this.handleKeyboard}
- placeholder={this.props.placeholder}
- rows={2}
- value={this.state.textComment}
- />
- </div>
- <div className="issue-comment-form-footer">
- <div className="issue-comment-form-actions">
- <Button
- className="js-issue-comment-submit little-spacer-right"
- disabled={this.state.textComment.trim().length < 1}
- onClick={this.handleCommentClick}>
- {comment && translate('save')}
- {!comment && translate('issue.comment.submit')}
- </Button>
- <ResetButtonLink className="js-issue-comment-cancel" onClick={this.handleCancelClick}>
- {autoTriggered && !showCommentsInPopup ? translate('skip') : translate('cancel')}
- </ResetButtonLink>
- </div>
- <div className="issue-comment-form-tips">
- <FormattingTips />
- </div>
- </div>
+ <CommentForm
+ placeholder={this.props.placeholder}
+ onCancel={this.handleCancelClick}
+ onSaveComment={this.props.onComment}
+ showFormatHelp={true}
+ comment={comment?.markdown}
+ autoTriggered={autoTriggered}
+ />
</div>
</DropdownOverlay>
);
diff --git a/server/sonar-web/src/main/js/components/issue/popups/CommentTile.tsx b/server/sonar-web/src/main/js/components/issue/popups/CommentTile.tsx
index 10f8908dc33..c94c741b439 100644
--- a/server/sonar-web/src/main/js/components/issue/popups/CommentTile.tsx
+++ b/server/sonar-web/src/main/js/components/issue/popups/CommentTile.tsx
@@ -18,13 +18,13 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { KeyboardKeys } from '../../../helpers/keycodes';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { sanitizeString } from '../../../helpers/sanitize';
import { IssueComment } from '../../../types/types';
-import { Button, DeleteButton, EditButton, ResetButtonLink } from '../../controls/buttons';
+import { DeleteButton, EditButton } from '../../controls/buttons';
import DateTimeFormatter from '../../intl/DateTimeFormatter';
import Avatar from '../../ui/Avatar';
+import CommentForm from './CommentForm';
interface CommentTileProps {
comment: IssueComment;
@@ -34,46 +34,31 @@ interface CommentTileProps {
interface CommentTileState {
showEditArea: boolean;
- editedComment: string;
}
export default class CommentTile extends React.PureComponent<CommentTileProps, CommentTileState> {
state = {
- showEditArea: false,
- editedComment: ''
+ showEditArea: false
};
handleEditClick = () => {
- const { comment } = this.props;
const { showEditArea } = this.state;
- const editedComment = !showEditArea ? comment.markdown : '';
- this.setState({ showEditArea: !showEditArea, editedComment });
+ this.setState({ showEditArea: !showEditArea });
};
- handleSaveClick = () => {
+ handleSaveClick = (editedComment: string) => {
const { comment } = this.props;
- const { editedComment } = this.state;
this.props.onEdit(comment.key, editedComment);
- this.setState({ showEditArea: false, editedComment: '' });
+ this.setState({ showEditArea: false });
};
handleCancelClick = () => {
this.setState({ showEditArea: false });
};
- handleEditCommentChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
- this.setState({ editedComment: event.target.value });
- };
-
- handleKeyboard = (event: React.KeyboardEvent) => {
- if (event.nativeEvent.key === KeyboardKeys.Enter && (event.metaKey || event.ctrlKey)) {
- this.handleSaveClick();
- }
- };
-
render() {
const { comment } = this.props;
- const { showEditArea, editedComment } = this.state;
+ const { showEditArea } = this.state;
const author = comment.authorName || comment.author;
const displayName =
comment.authorActive === false && author
@@ -103,31 +88,13 @@ export default class CommentTile extends React.PureComponent<CommentTileProps, C
/>
)}
{showEditArea && (
- <div className="edit-form flex-1">
- <div className="issue-comment-form-text">
- <textarea
- autoFocus={true}
- onChange={this.handleEditCommentChange}
- onKeyDown={this.handleKeyboard}
- rows={2}
- value={editedComment}
- />
- </div>
- <div className="issue-comment-form-footer">
- <div className="issue-comment-form-actions little-padded-left">
- <Button
- className="js-issue-comment-submit little-spacer-right"
- disabled={editedComment.trim().length < 1}
- onClick={this.handleSaveClick}>
- {translate('save')}
- </Button>
- <ResetButtonLink
- className="js-issue-comment-cancel"
- onClick={this.handleCancelClick}>
- {translate('cancel')}
- </ResetButtonLink>
- </div>
- </div>
+ <div className="flex-1">
+ <CommentForm
+ onCancel={this.handleCancelClick}
+ onSaveComment={this.handleSaveClick}
+ showFormatHelp={false}
+ comment={comment.markdown}
+ />
</div>
)}
{comment.updatable && (
diff --git a/server/sonar-web/src/main/js/components/issue/popups/CommentsListPopup.tsx b/server/sonar-web/src/main/js/components/issue/popups/CommentsListPopup.tsx
new file mode 100644
index 00000000000..2691486180f
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/issue/popups/CommentsListPopup.tsx
@@ -0,0 +1,78 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { DropdownOverlay } from '../../../components/controls/Dropdown';
+import { PopupPlacement } from '../../../components/ui/popups';
+import { IssueComment } from '../../../types/types';
+import CommentForm from './CommentForm';
+import CommentList from './CommentList';
+
+export interface Props {
+ onAddComment: (text: string) => void;
+ toggleComment: (visible: boolean) => void;
+ deleteComment: (comment: string) => void;
+ onEdit: (comment: string, text: string) => void;
+ placeholder: string;
+ placement?: PopupPlacement;
+ autoTriggered?: boolean;
+ comments?: IssueComment[];
+ canComment: boolean;
+}
+
+export default class CommentListPopup extends React.PureComponent<Props, {}> {
+ handleCommentClick = (comment: string) => {
+ const { autoTriggered } = this.props;
+
+ this.props.onAddComment(comment);
+
+ if (autoTriggered) {
+ this.props.toggleComment(false);
+ }
+ };
+
+ handleCancelClick = () => {
+ this.props.toggleComment(false);
+ };
+
+ render() {
+ const { comments, placeholder, autoTriggered, canComment } = this.props;
+
+ return (
+ <DropdownOverlay placement={this.props.placement}>
+ <div className="issue-comment-bubble-popup">
+ <CommentList
+ comments={comments}
+ deleteComment={this.props.deleteComment}
+ onEdit={this.props.onEdit}
+ />
+ {canComment && (
+ <CommentForm
+ autoTriggered={autoTriggered}
+ placeholder={placeholder}
+ onCancel={this.handleCancelClick}
+ onSaveComment={this.handleCommentClick}
+ showFormatHelp={true}
+ />
+ )}
+ </div>
+ </DropdownOverlay>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/components/issue/popups/__tests__/CommentPopup-test.tsx b/server/sonar-web/src/main/js/components/issue/popups/__tests__/CommentPopup-test.tsx
index bffeda3387a..dd3e8007aa7 100644
--- a/server/sonar-web/src/main/js/components/issue/popups/__tests__/CommentPopup-test.tsx
+++ b/server/sonar-web/src/main/js/components/issue/popups/__tests__/CommentPopup-test.tsx
@@ -17,71 +17,33 @@
* 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 { screen } from '@testing-library/dom';
+import userEvent from '@testing-library/user-event';
import * as React from 'react';
-import { KeyboardKeys } from '../../../../helpers/keycodes';
-import { click, mockEvent } from '../../../../helpers/testUtils';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
import CommentPopup, { CommentPopupProps } from '../CommentPopup';
-it('should render the comment popup correctly without existing comment', () => {
- expect(shallowRender()).toMatchSnapshot();
-});
-
-it('should render the comment popup correctly when changing a comment', () => {
- expect(shallowRender({ comment: { markdown: '*test*' } })).toMatchSnapshot();
-});
-
-it('should render not allow to send comment with only spaces', () => {
+it('should trigger comment change', async () => {
+ const user = userEvent.setup();
const onComment = jest.fn();
- const wrapper = shallowRender({ onComment });
- click(wrapper.find('.js-issue-comment-submit'));
- expect(onComment.mock.calls.length).toBe(0);
- wrapper.setState({ textComment: 'mycomment' });
- click(wrapper.find('.js-issue-comment-submit'));
- expect(onComment.mock.calls.length).toBe(1);
-});
-
-it('should render the alternative cancel button label', () => {
- const wrapper = shallowRender({ autoTriggered: true });
- expect(
- wrapper
- .find('.js-issue-comment-cancel')
- .childAt(0)
- .text()
- ).toBe('skip');
-});
-
-it('should handle ctrl+enter', () => {
- const onComment = jest.fn();
- const wrapper = shallowRender({ comment: { markdown: 'yes' }, onComment });
-
- wrapper
- .instance()
- .handleKeyboard(mockEvent({ ctrlKey: true, nativeEvent: { key: KeyboardKeys.Enter } }));
-
- expect(onComment).toBeCalled();
-});
-
-it('should stopPropagation for arrow keys events', () => {
- const wrapper = shallowRender();
+ const toggleComment = jest.fn();
+ shallowRender({ onComment, toggleComment });
- const event = mockEvent({
- nativeEvent: { key: KeyboardKeys.UpArrow },
- stopPropagation: jest.fn()
- });
- wrapper.instance().handleKeyboard(event);
+ expect(await screen.findByRole('textbox')).toHaveFocus();
+ await user.keyboard('test');
+ await user.keyboard('{Control>}{Enter}{/Control}');
+ expect(onComment).toHaveBeenCalledWith('test');
- expect(event.stopPropagation).toBeCalled();
+ await user.click(screen.getByRole('button', { name: 'issue.comment.add_comment.cancel' }));
+ expect(toggleComment).toHaveBeenCalledWith(false);
});
function shallowRender(overrides: Partial<CommentPopupProps> = {}) {
- return shallow<CommentPopup>(
+ return renderComponent(
<CommentPopup
onComment={jest.fn()}
placeholder="placeholder test"
toggleComment={jest.fn()}
- deleteComment={jest.fn()}
- onEdit={jest.fn()}
{...overrides}
/>
);
diff --git a/server/sonar-web/src/main/js/components/issue/popups/__tests__/SetIssueTagsPopup-test.tsx b/server/sonar-web/src/main/js/components/issue/popups/__tests__/SetIssueTagsPopup-test.tsx
deleted file mode 100644
index 1f5c3572e05..00000000000
--- a/server/sonar-web/src/main/js/components/issue/popups/__tests__/SetIssueTagsPopup-test.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 SetIssueTagsPopup from '../SetIssueTagsPopup';
-
-it('should render tags popup correctly', () => {
- const element = shallow(<SetIssueTagsPopup selectedTags={['mytag']} setTags={jest.fn()} />);
- element.setState({ searchResult: ['mytag', 'test', 'second'] });
- expect(element).toMatchSnapshot();
-});
diff --git a/server/sonar-web/src/main/js/components/issue/popups/__tests__/SetSeverityPopup-test.tsx b/server/sonar-web/src/main/js/components/issue/popups/__tests__/SetSeverityPopup-test.tsx
deleted file mode 100644
index 5b2040e23bb..00000000000
--- a/server/sonar-web/src/main/js/components/issue/popups/__tests__/SetSeverityPopup-test.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 SetSeverityPopup from '../SetSeverityPopup';
-
-it('should render tags popup correctly', () => {
- const element = shallow(<SetSeverityPopup issue={{ severity: 'MAJOR' }} onSelect={jest.fn()} />);
- expect(element).toMatchSnapshot();
-});
diff --git a/server/sonar-web/src/main/js/components/issue/popups/__tests__/SetTransitionPopup-test.tsx b/server/sonar-web/src/main/js/components/issue/popups/__tests__/SetTransitionPopup-test.tsx
deleted file mode 100644
index 0103c77e306..00000000000
--- a/server/sonar-web/src/main/js/components/issue/popups/__tests__/SetTransitionPopup-test.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { hasMessage } from '../../../../helpers/l10n';
-import SetTransitionPopup, { Props } from '../SetTransitionPopup';
-
-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}
- />
- );
-}
diff --git a/server/sonar-web/src/main/js/components/issue/popups/__tests__/SetTypePopup-test.tsx b/server/sonar-web/src/main/js/components/issue/popups/__tests__/SetTypePopup-test.tsx
deleted file mode 100644
index 2c6b4c49e29..00000000000
--- a/server/sonar-web/src/main/js/components/issue/popups/__tests__/SetTypePopup-test.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 SetTypePopup from '../SetTypePopup';
-
-it('should render tags popup correctly', () => {
- const element = shallow(<SetTypePopup issue={{ type: 'BUG' }} onSelect={jest.fn()} />);
- expect(element).toMatchSnapshot();
-});
diff --git a/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/CommentPopup-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/CommentPopup-test.tsx.snap
deleted file mode 100644
index 9fc6c4cc228..00000000000
--- a/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/CommentPopup-test.tsx.snap
+++ /dev/null
@@ -1,95 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render the comment popup correctly when changing a comment 1`] = `
-<DropdownOverlay>
- <div
- className="issue-comment-bubble-popup"
- >
- <div
- className="issue-comment-form-text"
- >
- <textarea
- autoFocus={true}
- onChange={[Function]}
- onKeyDown={[Function]}
- placeholder="placeholder test"
- rows={2}
- value="*test*"
- />
- </div>
- <div
- className="issue-comment-form-footer"
- >
- <div
- className="issue-comment-form-actions"
- >
- <Button
- className="js-issue-comment-submit little-spacer-right"
- disabled={false}
- onClick={[Function]}
- >
- save
- </Button>
- <ResetButtonLink
- className="js-issue-comment-cancel"
- onClick={[Function]}
- >
- cancel
- </ResetButtonLink>
- </div>
- <div
- className="issue-comment-form-tips"
- >
- <FormattingTips />
- </div>
- </div>
- </div>
-</DropdownOverlay>
-`;
-
-exports[`should render the comment popup correctly without existing comment 1`] = `
-<DropdownOverlay>
- <div
- className="issue-comment-bubble-popup"
- >
- <div
- className="issue-comment-form-text"
- >
- <textarea
- autoFocus={true}
- onChange={[Function]}
- onKeyDown={[Function]}
- placeholder="placeholder test"
- rows={2}
- value=""
- />
- </div>
- <div
- className="issue-comment-form-footer"
- >
- <div
- className="issue-comment-form-actions"
- >
- <Button
- className="js-issue-comment-submit little-spacer-right"
- disabled={true}
- onClick={[Function]}
- >
- issue.comment.submit
- </Button>
- <ResetButtonLink
- className="js-issue-comment-cancel"
- onClick={[Function]}
- >
- cancel
- </ResetButtonLink>
- </div>
- <div
- className="issue-comment-form-tips"
- >
- <FormattingTips />
- </div>
- </div>
- </div>
-</DropdownOverlay>
-`;
diff --git a/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetIssueTagsPopup-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetIssueTagsPopup-test.tsx.snap
deleted file mode 100644
index a5eb2aafcac..00000000000
--- a/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetIssueTagsPopup-test.tsx.snap
+++ /dev/null
@@ -1,25 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render tags popup correctly 1`] = `
-<DropdownOverlay
- placement="bottom-right"
->
- <TagsSelector
- listSize={10}
- onSearch={[Function]}
- onSelect={[Function]}
- onUnselect={[Function]}
- selectedTags={
- Array [
- "mytag",
- ]
- }
- tags={
- Array [
- "test",
- "second",
- ]
- }
- />
-</DropdownOverlay>
-`;
diff --git a/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetSeverityPopup-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetSeverityPopup-test.tsx.snap
deleted file mode 100644
index 405a967b10d..00000000000
--- a/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetSeverityPopup-test.tsx.snap
+++ /dev/null
@@ -1,75 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render tags popup correctly 1`] = `
-<DropdownOverlay>
- <SelectList
- currentItem="MAJOR"
- items={
- Array [
- "BLOCKER",
- "CRITICAL",
- "MAJOR",
- "MINOR",
- "INFO",
- ]
- }
- onSelect={[MockFunction]}
- >
- <SelectListItem
- className="display-flex-center"
- item="BLOCKER"
- key="BLOCKER"
- >
- <SeverityIcon
- className="little-spacer-right"
- severity="BLOCKER"
- />
- severity.BLOCKER
- </SelectListItem>
- <SelectListItem
- className="display-flex-center"
- item="CRITICAL"
- key="CRITICAL"
- >
- <SeverityIcon
- className="little-spacer-right"
- severity="CRITICAL"
- />
- severity.CRITICAL
- </SelectListItem>
- <SelectListItem
- className="display-flex-center"
- item="MAJOR"
- key="MAJOR"
- >
- <SeverityIcon
- className="little-spacer-right"
- severity="MAJOR"
- />
- severity.MAJOR
- </SelectListItem>
- <SelectListItem
- className="display-flex-center"
- item="MINOR"
- key="MINOR"
- >
- <SeverityIcon
- className="little-spacer-right"
- severity="MINOR"
- />
- severity.MINOR
- </SelectListItem>
- <SelectListItem
- className="display-flex-center"
- item="INFO"
- key="INFO"
- >
- <SeverityIcon
- className="little-spacer-right"
- severity="INFO"
- />
- severity.INFO
- </SelectListItem>
- </SelectList>
-</DropdownOverlay>
-`;
diff --git a/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetTransitionPopup-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetTransitionPopup-test.tsx.snap
deleted file mode 100644
index 70a86d8ea52..00000000000
--- a/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetTransitionPopup-test.tsx.snap
+++ /dev/null
@@ -1,157 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render transition popup correctly 1`] = `
-<DropdownOverlay>
- <SelectList
- currentItem="confirm"
- items={
- Array [
- "confirm",
- "resolve",
- "falsepositive",
- "wontfix",
- ]
- }
- onSelect={[MockFunction]}
- >
- <SelectListItem
- item="confirm"
- key="confirm"
- title={
- <FormattedMessage
- defaultMessage="issue.transition.confirm.description"
- id="issue.transition.confirm.description"
- values={
- Object {
- "community_plug_link": <a
- href="https://community.sonarsource.com/"
- rel="noopener noreferrer"
- target="_blank"
- >
- issue.transition.community_plug_link
- </a>,
- }
- }
- />
- }
- >
- issue.transition.confirm
- </SelectListItem>
- <SelectListItem
- item="resolve"
- key="resolve"
- title={
- <FormattedMessage
- defaultMessage="issue.transition.resolve.description"
- id="issue.transition.resolve.description"
- values={
- Object {
- "community_plug_link": <a
- href="https://community.sonarsource.com/"
- rel="noopener noreferrer"
- target="_blank"
- >
- issue.transition.community_plug_link
- </a>,
- }
- }
- />
- }
- >
- issue.transition.resolve
- </SelectListItem>
- <SelectListItem
- item="falsepositive"
- key="falsepositive"
- title={
- <FormattedMessage
- defaultMessage="issue.transition.falsepositive.description"
- id="issue.transition.falsepositive.description"
- values={
- Object {
- "community_plug_link": <a
- href="https://community.sonarsource.com/"
- rel="noopener noreferrer"
- target="_blank"
- >
- issue.transition.community_plug_link
- </a>,
- }
- }
- />
- }
- >
- issue.transition.falsepositive
- </SelectListItem>
- <SelectListItem
- item="wontfix"
- key="wontfix"
- title={
- <FormattedMessage
- defaultMessage="issue.transition.wontfix.description"
- id="issue.transition.wontfix.description"
- values={
- Object {
- "community_plug_link": <a
- href="https://community.sonarsource.com/"
- rel="noopener noreferrer"
- target="_blank"
- >
- issue.transition.community_plug_link
- </a>,
- }
- }
- />
- }
- >
- issue.transition.wontfix
- </SelectListItem>
- </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={
- <FormattedMessage
- defaultMessage="issue.transition.confirm.description"
- id="issue.transition.confirm.description"
- values={
- Object {
- "community_plug_link": <a
- href="https://community.sonarsource.com/"
- rel="noopener noreferrer"
- target="_blank"
- >
- issue.transition.community_plug_link
- </a>,
- }
- }
- />
- }
- >
- issue.transition.confirm
- </SelectListItem>
- </SelectList>
-</DropdownOverlay>
-`;
diff --git a/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetTypePopup-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetTypePopup-test.tsx.snap
deleted file mode 100644
index 1073a5fd23c..00000000000
--- a/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetTypePopup-test.tsx.snap
+++ /dev/null
@@ -1,51 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render tags popup correctly 1`] = `
-<DropdownOverlay>
- <SelectList
- currentItem="BUG"
- items={
- Array [
- "BUG",
- "VULNERABILITY",
- "CODE_SMELL",
- ]
- }
- onSelect={[MockFunction]}
- >
- <SelectListItem
- className="display-flex-center"
- item="BUG"
- key="BUG"
- >
- <IssueTypeIcon
- className="little-spacer-right"
- query="BUG"
- />
- issue.type.BUG
- </SelectListItem>
- <SelectListItem
- className="display-flex-center"
- item="VULNERABILITY"
- key="VULNERABILITY"
- >
- <IssueTypeIcon
- className="little-spacer-right"
- query="VULNERABILITY"
- />
- issue.type.VULNERABILITY
- </SelectListItem>
- <SelectListItem
- className="display-flex-center"
- item="CODE_SMELL"
- key="CODE_SMELL"
- >
- <IssueTypeIcon
- className="little-spacer-right"
- query="CODE_SMELL"
- />
- issue.type.CODE_SMELL
- </SelectListItem>
- </SelectList>
-</DropdownOverlay>
-`;
diff --git a/server/sonar-web/src/main/js/types/issues.ts b/server/sonar-web/src/main/js/types/issues.ts
index 33e71519a35..2e12d798318 100644
--- a/server/sonar-web/src/main/js/types/issues.ts
+++ b/server/sonar-web/src/main/js/types/issues.ts
@@ -57,6 +57,7 @@ export interface RawIssue {
line?: number;
project: string;
rule: string;
+ resolution?: string;
message?: string;
severity: string;
status: string;
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index ee93a308e00..d337af4e3ba 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -820,11 +820,13 @@ issue.assign.formlink=Assign
issue.assign.to_me=to me
issue.quick_fix_available_with_sonarlint=Quick fix available in {link}
issue.comment.add_comment=Add Comment
+issue.comment.add_comment.cancel=Cancel adding comment
issue.comment.formlink=Comment
issue.comment.submit=Comment
issue.comment.explain_why=Consider explaining why
issue.comment.posted_on=Comment posted on
issue.comment.edit=Edit comment
+issue.comment.edit.cancel=Cancel editing comment
issue.comment.delete=Delete comment
issue.comment.delete_confirm_message=Do you want to delete this comment?
issue.manual_vulnerability=Manual