diff options
author | Revanshu Paliwal <revanshu.paliwal@sonarsource.com> | 2022-08-02 14:55:42 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-08-03 20:03:24 +0000 |
commit | aafb356bce29f3c5aa037a661d8e4deddb3ec940 (patch) | |
tree | 02848c65e058e9313bf7b5b523b80ded7465b481 /server/sonar-web | |
parent | 4c176a2f16fccfbe876cfa6f3a1703026fbc986b (diff) | |
download | sonarqube-aafb356bce29f3c5aa037a661d8e4deddb3ec940.tar.gz sonarqube-aafb356bce29f3c5aa037a661d8e4deddb3ec940.zip |
SONAR-16538 Adding new comment list ui for issue comment popup
Diffstat (limited to 'server/sonar-web')
14 files changed, 382 insertions, 7 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 e42cdf6eef8..f40a56052a6 100644 --- a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts @@ -52,6 +52,9 @@ import { import { NoticeType } from '../../types/users'; import { getComponentForSourceViewer, getSources } from '../components'; import { + addIssueComment, + deleteIssueComment, + editIssueComment, getIssueFlowSnippets, searchIssues, searchIssueTags, @@ -224,6 +227,9 @@ export default class IssuesServiceMock { (setIssueSeverity as jest.Mock).mockImplementation(this.handleSetIssueSeverity); (setIssueTransition as jest.Mock).mockImplementation(this.handleSetIssueTransition); (setIssueTags as jest.Mock).mockImplementation(this.handleSetIssueTags); + (addIssueComment as jest.Mock).mockImplementation(this.handleAddComment); + (editIssueComment as jest.Mock).mockImplementation(this.handleEditComment); + (deleteIssueComment as jest.Mock).mockImplementation(this.handleDeleteComment); (searchUsers as jest.Mock).mockImplementation(this.handleSearchUsers); (searchIssueTags as jest.Mock).mockImplementation(this.handleSearchIssueTags); } @@ -387,6 +393,54 @@ export default class IssuesServiceMock { return this.getActionsResponse({ tags: tagsArr }, data.issue); }; + handleAddComment = (data: { issue: string; text: string }) => { + // For comment its little more complex to get comment Id + return this.getActionsResponse( + { + comments: [ + { + createdAt: '2022-07-28T11:30:04+0200', + htmlText: data.text, + key: '1234', + login: 'admin', + markdown: data.text, + updatable: true + } + ] + }, + data.issue + ); + }; + + handleEditComment = (data: { comment: string; text: string }) => { + // For comment its little more complex to get comment Id + return this.getActionsResponse( + { + comments: [ + { + createdAt: '2022-07-28T11:30:04+0200', + htmlText: data.text, + key: '1234', + login: 'admin', + markdown: data.text, + updatable: true + } + ] + }, + 'issue2' + ); + }; + + handleDeleteComment = () => { + // For comment its little more complex to get comment Id + return this.getActionsResponse( + { + comments: [] + }, + 'issue2' + ); + }; + handleSearchUsers = () => { return this.reply({ users: [mockLoggedInUser()] }); }; 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 204bb4be7ff..12eba71e38f 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 @@ -254,6 +254,47 @@ it('should be able to perform action on issues', async () => { await user.keyboard('{ArrowUp}{enter}'); expect(screen.getByText('luke')).toBeInTheDocument(); + // adding comment to the issue + expect( + screen.getByRole('button', { + name: `issue.comment.add_comment` + }) + ).toBeInTheDocument(); + + await user.click( + screen.getByRole('button', { + name: `issue.comment.add_comment` + }) + ); + expect(screen.getByText('issue.comment.submit')).toBeInTheDocument(); + await user.keyboard('comment'); + await user.click(screen.getByText('issue.comment.submit')); + expect(screen.getByText('comment')).toBeInTheDocument(); + + // 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.getByText('save')); + expect(screen.getByText('New comment')).toBeInTheDocument(); + + // deleting the comment + expect(screen.getByRole('button', { name: 'issue.comment.delete' })).toBeInTheDocument(); + await user.click(screen.getByRole('button', { name: 'issue.comment.delete' })); + expect(screen.queryByText('New comment')).not.toBeInTheDocument(); + + // adding comment using keyboard + await user.click(screen.getByRole('textbox')); + await user.keyboard('comment'); + await user.keyboard('{Control>}{enter}{/Control}'); + expect(screen.getByText('comment')).toBeInTheDocument(); + + // editing the comment using keyboard + await user.click(screen.getByRole('button', { name: 'issue.comment.edit' })); + await user.keyboard('New '); + await user.keyboard('{Control>}{enter}{/Control}'); + expect(screen.getByText('New comment')).toBeInTheDocument(); + // changing tags expect(screen.getByText('issue.no_tag')).toBeInTheDocument(); await user.click(screen.getByText('issue.no_tag')); 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 2fe66f71d80..355e5e5586e 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 @@ -19,7 +19,7 @@ */ import * as React from 'react'; import { Link } from 'react-router-dom'; -import { setIssueAssignee } from '../../../api/issues'; +import { deleteIssueComment, editIssueComment, setIssueAssignee } from '../../../api/issues'; import LinkIcon from '../../../components/icons/LinkIcon'; import { updateIssue } from '../../../components/issue/actions'; import IssueActionsBar from '../../../components/issue/components/IssueActionsBar'; @@ -65,6 +65,14 @@ export default class IssueHeader extends React.PureComponent<Props, State> { this.setState({ issuePopupName: name }); }; + deleteComment = (comment: string) => { + updateIssue(this.props.onIssueChange, deleteIssueComment({ comment })); + }; + + editComment = (comment: string, text: string) => { + updateIssue(this.props.onIssueChange, editIssueComment({ comment, text })); + }; + handleAssignement = (login: string) => { const { issue } = this.props; if (issue.assignee !== login) { @@ -160,6 +168,9 @@ 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/Issue.css b/server/sonar-web/src/main/js/components/issue/Issue.css index 7567b6de9ae..4437af36bad 100644 --- a/server/sonar-web/src/main/js/components/issue/Issue.css +++ b/server/sonar-web/src/main/js/components/issue/Issue.css @@ -271,3 +271,12 @@ .issue-get-perma-link { flex-shrink: 0; } + +.issue-comment-list-wrapper { + max-height: 400px; + overflow-y: scroll; +} + +.issue-comment-tile { + background-color: var(--barBackgroundColor); +} 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 e50dc6a900e..b144ef317c6 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,7 +36,10 @@ 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; } interface State { @@ -88,7 +91,7 @@ export default class IssueActionsBar extends React.PureComponent<Props, State> { }; render() { - const { issue, className } = this.props; + const { issue, className, showCommentsInPopup } = this.props; const canAssign = issue.actions.includes('assign'); const canComment = issue.actions.includes('comment'); const canSetSeverity = issue.actions.includes('set_severity'); @@ -153,6 +156,10 @@ export default class IssueActionsBar extends React.PureComponent<Props, State> { issueKey={issue.key} onChange={this.props.onChange} toggleComment={this.toggleComment} + comments={issue.comments} + deleteComment={this.props.deleteComment} + onEdit={this.props.onEdit} + showCommentsInPopup={showCommentsInPopup} /> )} </div> 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 4a4bb9d8151..43332b92dae 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 @@ -22,7 +22,7 @@ import { addIssueComment } from '../../../api/issues'; import { ButtonLink } from '../../../components/controls/buttons'; import Toggler from '../../../components/controls/Toggler'; import { translate } from '../../../helpers/l10n'; -import { Issue } from '../../../types/types'; +import { Issue, IssueComment } from '../../../types/types'; import { updateIssue } from '../actions'; import CommentPopup from '../popups/CommentPopup'; @@ -33,12 +33,19 @@ interface Props { 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; } export default class IssueCommentAction extends React.PureComponent<Props> { addComment = (text: string) => { + const { showCommentsInPopup } = this.props; updateIssue(this.props.onChange, addIssueComment({ issue: this.props.issueKey, text })); - this.props.toggleComment(false); + if (!showCommentsInPopup) { + this.props.toggleComment(false); + } }; handleCommentClick = () => { @@ -50,6 +57,7 @@ export default class IssueCommentAction extends React.PureComponent<Props> { }; render() { + const { comments, showCommentsInPopup } = this.props; return ( <div className="issue-meta dropdown"> <Toggler @@ -62,6 +70,10 @@ export default class IssueCommentAction extends React.PureComponent<Props> { onComment={this.addComment} placeholder={this.props.commentPlaceholder} toggleComment={this.props.toggleComment} + comments={comments} + deleteComment={this.props.deleteComment} + onEdit={this.props.onEdit} + showCommentsInPopup={showCommentsInPopup} /> }> <ButtonLink @@ -69,7 +81,12 @@ export default class IssueCommentAction extends React.PureComponent<Props> { aria-label={translate('issue.comment.add_comment')} className="issue-action js-issue-comment" onClick={this.handleCommentClick}> - <span className="issue-meta-label">{translate('issue.comment.formlink')}</span> + <span className="issue-meta-label"> + {showCommentsInPopup && comments && comments.length > 0 && ( + <span className="little-spacer-right">{comments.length}</span> + )} + {translate('issue.comment.formlink')} + </span> </ButtonLink> </Toggler> </div> 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 00655ccdaa2..84cd9848c54 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,6 +32,8 @@ it('should render issue correctly', () => { onAssign={jest.fn()} onChange={jest.fn()} togglePopup={jest.fn()} + deleteComment={jest.fn()} + onEdit={jest.fn()} /> ); expect(element).toMatchSnapshot(); @@ -44,6 +46,8 @@ it('should render security hotspot correctly', () => { onAssign={jest.fn()} onChange={jest.fn()} togglePopup={jest.fn()} + deleteComment={jest.fn()} + onEdit={jest.fn()} /> ); expect(element).toMatchSnapshot(); @@ -56,6 +60,8 @@ it('should render commentable correctly', () => { onAssign={jest.fn()} onChange={jest.fn()} togglePopup={jest.fn()} + deleteComment={jest.fn()} + onEdit={jest.fn()} /> ); expect(element).toMatchSnapshot(); @@ -68,6 +74,8 @@ it('should render effort correctly', () => { onAssign={jest.fn()} onChange={jest.fn()} togglePopup={jest.fn()} + deleteComment={jest.fn()} + onEdit={jest.fn()} /> ); expect(element).toMatchSnapshot(); @@ -84,6 +92,8 @@ 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 index 1af12e13bf6..cafec1f9c41 100644 --- 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 @@ -42,6 +42,8 @@ function shallowRender(props: Partial<IssueCommentAction['props']> = {}) { 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__/__snapshots__/IssueActionsBar-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueActionsBar-test.tsx.snap index 574359b316b..5bf3b691fb5 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 @@ -182,8 +182,10 @@ exports[`should render commentable correctly 1`] = ` <IssueCommentAction 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 index 786ce9ab8ea..602e0060008 100644 --- 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 @@ -10,7 +10,9 @@ exports[`should open the popup when the button is clicked 1`] = ` open={true} overlay={ <CommentPopup + deleteComment={[MockFunction]} onComment={[Function]} + onEdit={[MockFunction]} placeholder="" toggleComment={ [MockFunction] { @@ -54,7 +56,9 @@ exports[`should render correctly 1`] = ` open={false} overlay={ <CommentPopup + deleteComment={[MockFunction]} onComment={[Function]} + onEdit={[MockFunction]} placeholder="" toggleComment={[MockFunction]} /> diff --git a/server/sonar-web/src/main/js/components/issue/popups/CommentList.tsx b/server/sonar-web/src/main/js/components/issue/popups/CommentList.tsx new file mode 100644 index 00000000000..5c7f570eeb9 --- /dev/null +++ b/server/sonar-web/src/main/js/components/issue/popups/CommentList.tsx @@ -0,0 +1,49 @@ +/* + * 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 { IssueComment } from '../../../types/types'; +import CommentTile from './CommentTile'; + +interface CommentListProps { + comments?: IssueComment[]; + deleteComment: (comment: string) => void; + onEdit: (comment: string, text: string) => void; +} + +export default function CommentList(props: CommentListProps) { + const { comments } = props; + // sorting comment i.e showing newest on top + const sortedComments = comments?.sort( + (com1, com2) => + new Date(com2.createdAt || '').getTime() - new Date(com1.createdAt || '').getTime() + ); + return ( + <div className="issue-comment-list-wrapper"> + {sortedComments?.map(c => ( + <CommentTile + comment={c} + key={c.key} + handleDelete={props.deleteComment} + onEdit={props.onEdit} + /> + ))} + </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 8b0bae9e518..e5fcc5f7411 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 @@ -25,14 +25,19 @@ import { KeyboardKeys } from '../../../helpers/keycodes'; import { translate } from '../../../helpers/l10n'; import { IssueComment } from '../../../types/types'; import FormattingTips from '../../common/FormattingTips'; +import CommentList from './CommentList'; 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 { @@ -54,6 +59,7 @@ export default class CommentPopup extends React.PureComponent<CommentPopupProps, handleCommentClick = () => { if (this.state.textComment.trim().length > 0) { this.props.onComment(this.state.textComment); + this.setState({ textComment: '' }); } }; @@ -78,10 +84,18 @@ export default class CommentPopup extends React.PureComponent<CommentPopupProps, }; render() { - const { comment, autoTriggered } = this.props; + const { comment, autoTriggered, comments, showCommentsInPopup } = 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} @@ -102,7 +116,7 @@ export default class CommentPopup extends React.PureComponent<CommentPopupProps, {!comment && translate('issue.comment.submit')} </Button> <ResetButtonLink className="js-issue-comment-cancel" onClick={this.handleCancelClick}> - {autoTriggered ? translate('skip') : translate('cancel')} + {autoTriggered && !showCommentsInPopup ? translate('skip') : translate('cancel')} </ResetButtonLink> </div> <div className="issue-comment-form-tips"> 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 new file mode 100644 index 00000000000..10f8908dc33 --- /dev/null +++ b/server/sonar-web/src/main/js/components/issue/popups/CommentTile.tsx @@ -0,0 +1,153 @@ +/* + * 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, translateWithParameters } from '../../../helpers/l10n'; +import { sanitizeString } from '../../../helpers/sanitize'; +import { IssueComment } from '../../../types/types'; +import { Button, DeleteButton, EditButton, ResetButtonLink } from '../../controls/buttons'; +import DateTimeFormatter from '../../intl/DateTimeFormatter'; +import Avatar from '../../ui/Avatar'; + +interface CommentTileProps { + comment: IssueComment; + handleDelete: (commentKey: string) => void; + onEdit: (comment: string, text: string) => void; +} + +interface CommentTileState { + showEditArea: boolean; + editedComment: string; +} + +export default class CommentTile extends React.PureComponent<CommentTileProps, CommentTileState> { + state = { + showEditArea: false, + editedComment: '' + }; + + handleEditClick = () => { + const { comment } = this.props; + const { showEditArea } = this.state; + const editedComment = !showEditArea ? comment.markdown : ''; + this.setState({ showEditArea: !showEditArea, editedComment }); + }; + + handleSaveClick = () => { + const { comment } = this.props; + const { editedComment } = this.state; + this.props.onEdit(comment.key, editedComment); + this.setState({ showEditArea: false, editedComment: '' }); + }; + + 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 author = comment.authorName || comment.author; + const displayName = + comment.authorActive === false && author + ? translateWithParameters('user.x_deleted', author) + : author; + return ( + <div className="issue-comment-tile spacer-bottom padded"> + <div className="display-flex-center"> + <div className="issue-comment-author display-flex-center" title={displayName}> + <Avatar + className="little-spacer-right" + hash={comment.authorAvatar} + name={author} + size={24} + /> + {displayName} + </div> + <span className="little-spacer-left little-spacer-right">-</span> + <DateTimeFormatter date={comment.createdAt} /> + </div> + <div className="spacer-top display-flex-space-between"> + {!showEditArea && ( + <div + className="flex-1 markdown" + // eslint-disable-next-line react/no-danger + dangerouslySetInnerHTML={{ __html: sanitizeString(comment.htmlText) }} + /> + )} + {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> + )} + {comment.updatable && ( + <div> + <EditButton + aria-label={translate('issue.comment.edit')} + className="js-issue-comment-edit button-small" + onClick={this.handleEditClick} + /> + <DeleteButton + aria-label={translate('issue.comment.delete')} + className="js-issue-comment-delete button-small" + onClick={() => { + this.props.handleDelete(comment.key); + }} + /> + </div> + )} + </div> + </div> + ); + } +} 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 95ed70f1af0..bffeda3387a 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 @@ -80,6 +80,8 @@ function shallowRender(overrides: Partial<CommentPopupProps> = {}) { onComment={jest.fn()} placeholder="placeholder test" toggleComment={jest.fn()} + deleteComment={jest.fn()} + onEdit={jest.fn()} {...overrides} /> ); |