From 8da77e625548aca7508dbe23b614f7aa25791e47 Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Mon, 6 Sep 2021 15:18:53 +0200 Subject: [PATCH] SONAR-13086 Improve activity and comment layout for Security Hotspots --- .../security-hotspots/__tests__/utils-test.ts | 8 +- .../components/HotspotReviewHistory.tsx | 211 ++--- .../HotspotReviewHistoryAndComments.tsx | 110 +-- .../components/HotspotViewer.tsx | 29 +- .../components/HotspotViewerRenderer.tsx | 12 +- .../__tests__/HotspotReviewHistory-test.tsx | 175 +++- .../HotspotReviewHistoryAndComments-test.tsx | 40 +- .../__tests__/HotspotViewer-test.tsx | 12 +- .../__tests__/HotspotViewerRenderer-test.tsx | 4 +- .../HotspotReviewHistory-test.tsx.snap | 784 +++++++++++++----- ...spotReviewHistoryAndComments-test.tsx.snap | 371 ++++----- .../__snapshots__/HotspotViewer-test.tsx.snap | 8 +- .../HotspotViewerRenderer-test.tsx.snap | 18 - .../main/js/apps/security-hotspots/utils.ts | 2 +- .../src/main/js/helpers/mocks/issues.ts | 17 + .../js/helpers/mocks/security-hotspots.ts | 14 + .../resources/org/sonar/l10n/core.properties | 3 +- 17 files changed, 1136 insertions(+), 682 deletions(-) diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/utils-test.ts index f62e89a980e..295b6bc6714 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/utils-test.ts +++ b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/utils-test.ts @@ -191,14 +191,14 @@ describe('getHotspotReviewHistory', () => { const reviewHistory = getHotspotReviewHistory(hotspot); expect(reviewHistory.length).toBe(4); - expect(reviewHistory[0]).toEqual( + expect(reviewHistory[3]).toEqual( expect.objectContaining({ type: ReviewHistoryType.Creation, date: hotspot.creationDate, user: hotspot.authorUser }) ); - expect(reviewHistory[1]).toEqual( + expect(reviewHistory[2]).toEqual( expect.objectContaining({ type: ReviewHistoryType.Comment, date: commentElement.createdAt, @@ -206,7 +206,7 @@ describe('getHotspotReviewHistory', () => { html: commentElement.htmlText }) ); - expect(reviewHistory[2]).toEqual( + expect(reviewHistory[1]).toEqual( expect.objectContaining({ type: ReviewHistoryType.Comment, date: commentElement1.createdAt, @@ -214,7 +214,7 @@ describe('getHotspotReviewHistory', () => { html: commentElement1.htmlText }) ); - expect(reviewHistory[3]).toEqual( + expect(reviewHistory[0]).toEqual( expect.objectContaining({ type: ReviewHistoryType.Diff, date: changelogElement.creationDate, diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistory.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistory.tsx index fd320921e0f..e3cf11a8a23 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistory.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistory.tsx @@ -19,7 +19,7 @@ */ import * as classNames from 'classnames'; import * as React from 'react'; -import { Button, DeleteButton, EditButton } from '../../../components/controls/buttons'; +import { Button, ButtonLink, DeleteButton, EditButton } from '../../../components/controls/buttons'; import Dropdown, { DropdownOverlay } from '../../../components/controls/Dropdown'; import Toggler from '../../../components/controls/Toggler'; import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; @@ -36,112 +36,131 @@ export interface HotspotReviewHistoryProps { hotspot: Hotspot; onDeleteComment: (key: string) => void; onEditComment: (key: string, comment: string) => void; + onShowFullHistory: () => void; + showFullHistory: boolean; } +export const MAX_RECENT_ACTIVITY = 5; + export default function HotspotReviewHistory(props: HotspotReviewHistoryProps) { - const { hotspot } = props; - const reviewHistory = getHotspotReviewHistory(hotspot); + const { hotspot, showFullHistory } = props; + const fullReviewHistory = getHotspotReviewHistory(hotspot); const [editedCommentKey, setEditedCommentKey] = React.useState(''); + const reviewHistory = showFullHistory + ? fullReviewHistory + : fullReviewHistory.slice(0, MAX_RECENT_ACTIVITY); + return ( <> - {reviewHistory.map((historyElt, historyIndex) => { - const { user, type, diffs, date, html, key, updatable, markdown } = historyElt; - return ( -
0 })} - key={historyIndex}> -
- {user.name && ( - <> - - - {user.active ? user.name : translateWithParameters('user.x_deleted', user.name)} - - {type === ReviewHistoryType.Creation && ( - - {translate('hotspots.review_history.created')} - - )} - {type === ReviewHistoryType.Comment && ( - - {translate('hotspots.review_history.comment_added')} - - )} - - - - )} - -
- - {type === ReviewHistoryType.Diff && diffs && ( -
- {diffs.map((diff, diffIndex) => ( - - ))} +
    + {reviewHistory.map((historyElt, historyIndex) => { + const { user, type, diffs, date, html, key, updatable, markdown } = historyElt; + return ( +
  • 0 + })} + key={historyIndex}> +
    + {user.name && ( + <> + + + {user.active + ? user.name + : translateWithParameters('user.x_deleted', user.name)} + + {type === ReviewHistoryType.Creation && ( + + {translate('hotspots.review_history.created')} + + )} + {type === ReviewHistoryType.Comment && ( + + {translate('hotspots.review_history.comment_added')} + + )} + - + + )} +
    - )} - {type === ReviewHistoryType.Comment && key && html && markdown && ( -
    -
    - {updatable && ( -
    -
    - { - setEditedCommentKey(''); - }} - open={key === editedCommentKey} + {type === ReviewHistoryType.Diff && diffs && ( +
    + {diffs.map((diff, diffIndex) => ( + + ))} +
    + )} + + {type === ReviewHistoryType.Comment && key && html && markdown && ( +
    +
    + {updatable && ( +
    +
    + { + setEditedCommentKey(''); + }} + open={key === editedCommentKey} + overlay={ + + setEditedCommentKey('')} + onCommentEditSubmit={comment => { + setEditedCommentKey(''); + props.onEditComment(key, comment); + }} + /> + + }> + setEditedCommentKey(key)} + /> + +
    + setEditedCommentKey('')} overlay={ - - setEditedCommentKey('')} - onCommentEditSubmit={comment => { - setEditedCommentKey(''); - props.onEditComment(key, comment); - }} - /> - - }> - setEditedCommentKey(key)} - /> - +
    +

    {translate('issue.comment.delete_confirm_message')}

    + +
    + } + overlayPlacement={PopupPlacement.BottomRight}> + +
    - setEditedCommentKey('')} - overlay={ -
    -

    {translate('issue.comment.delete_confirm_message')}

    - -
    - } - overlayPlacement={PopupPlacement.BottomRight}> - -
    -
    - )} -
    - )} -
    - ); - })} + )} +
    + )} +
  • + ); + })} +
+ {!showFullHistory && fullReviewHistory.length > MAX_RECENT_ACTIVITY && ( + + {translate('show_all')} + + )} ); } diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistoryAndComments.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistoryAndComments.tsx index 42d9a2fbc92..b358850b3ca 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistoryAndComments.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistoryAndComments.tsx @@ -17,7 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import * as classNames from 'classnames'; import * as React from 'react'; import { commentSecurityHotspot, @@ -25,7 +24,7 @@ import { editSecurityHotspotComment } from '../../../api/security-hotspots'; import FormattingTips from '../../../components/common/FormattingTips'; -import { Button, ResetButtonLink } from '../../../components/controls/buttons'; +import { Button } from '../../../components/controls/buttons'; import { translate } from '../../../helpers/l10n'; import { isLoggedIn } from '../../../helpers/users'; import { Hotspot } from '../../../types/security-hotspots'; @@ -35,28 +34,28 @@ interface Props { currentUser: T.CurrentUser; hotspot: Hotspot; commentTextRef: React.RefObject; - commentVisible: boolean; onCommentUpdate: () => void; - onOpenComment: () => void; - onCloseComment: () => void; } interface State { comment: string; + showFullHistory: boolean; } export default class HotspotReviewHistoryAndComments extends React.PureComponent { constructor(props: Props) { super(props); this.state = { - comment: '' + comment: '', + showFullHistory: false }; } componentDidUpdate(prevProps: Props) { - if (prevProps.hotspot !== this.props.hotspot) { + if (prevProps.hotspot.key !== this.props.hotspot.key) { this.setState({ - comment: '' + comment: '', + showFullHistory: false }); } } @@ -65,15 +64,9 @@ export default class HotspotReviewHistoryAndComments extends React.PureComponent this.setState({ comment: event.target.value }); }; - handleCloseComment = () => { - this.setState({ comment: '' }); - this.props.onCloseComment(); - }; - handleSubmitComment = () => { return commentSecurityHotspot(this.props.hotspot.key, this.state.comment).then(() => { this.setState({ comment: '' }); - this.props.onCloseComment(); this.props.onCommentUpdate(); }); }; @@ -90,62 +83,49 @@ export default class HotspotReviewHistoryAndComments extends React.PureComponent }); }; + handleShowFullHistory = () => { + this.setState({ showFullHistory: true }); + }; + render() { - const { currentUser, hotspot, commentTextRef, commentVisible } = this.props; - const { comment } = this.state; + const { currentUser, hotspot, commentTextRef } = this.props; + const { comment, showFullHistory } = this.state; return ( - <> -

{translate('hotspot.section.activity')}

-
- - - {isLoggedIn(currentUser) && ( - <> -
-
+
+ {isLoggedIn(currentUser) && ( +
+
{translate('hotspots.comment.field')}
+