diff options
author | Mathieu Suen <mathieu.suen@sonarsource.com> | 2020-02-12 13:32:32 +0100 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2020-02-21 20:46:19 +0100 |
commit | 9bc1ac6000521a9a64c08c69100000d1f49cd383 (patch) | |
tree | 77ca6e62520727fbc7ac8258e6176d9e02c4c17e | |
parent | b381bc38fdf5c082ac0c0f12b45926b10c34ddb1 (diff) | |
download | sonarqube-9bc1ac6000521a9a64c08c69100000d1f49cd383.tar.gz sonarqube-9bc1ac6000521a9a64c08c69100000d1f49cd383.zip |
SONAR-12720 Put comment on bottom of hotspot review page and add a link from top.
24 files changed, 1678 insertions, 1570 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 174d4dbe7e2..8df41e695eb 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 @@ -188,16 +188,15 @@ describe('getHotspotReviewHistory', () => { }); const reviewHistory = getHotspotReviewHistory(hotspot); - expect(reviewHistory.history.length).toBe(4); - expect(reviewHistory.functionalCount).toBe(3); - expect(reviewHistory.history[0]).toEqual( + expect(reviewHistory.length).toBe(4); + expect(reviewHistory[0]).toEqual( expect.objectContaining({ type: ReviewHistoryType.Creation, date: hotspot.creationDate, user: hotspot.authorUser }) ); - expect(reviewHistory.history[1]).toEqual( + expect(reviewHistory[1]).toEqual( expect.objectContaining({ type: ReviewHistoryType.Comment, date: commentElement.createdAt, @@ -205,7 +204,7 @@ describe('getHotspotReviewHistory', () => { html: commentElement.htmlText }) ); - expect(reviewHistory.history[2]).toEqual( + expect(reviewHistory[2]).toEqual( expect.objectContaining({ type: ReviewHistoryType.Comment, date: commentElement1.createdAt, @@ -213,7 +212,7 @@ describe('getHotspotReviewHistory', () => { html: commentElement1.htmlText }) ); - expect(reviewHistory.history[3]).toEqual( + expect(reviewHistory[3]).toEqual( expect.objectContaining({ type: ReviewHistoryType.Diff, date: changelogElement.creationDate, diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerReviewHistoryTab.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistory.tsx index 07989ed42f7..1b94b1a590e 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerReviewHistoryTab.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistory.tsx @@ -17,83 +17,76 @@ * 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 { sanitize } from 'dompurify'; import * as React from 'react'; import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import IssueChangelogDiff from '../../../components/issue/components/IssueChangelogDiff'; import Avatar from '../../../components/ui/Avatar'; -import { Hotspot, ReviewHistoryElement, ReviewHistoryType } from '../../../types/security-hotspots'; -import HotspotViewerReviewHistoryTabCommentBox from './HotspotViewerReviewHistoryTabCommentBox'; +import { Hotspot, ReviewHistoryType } from '../../../types/security-hotspots'; +import { getHotspotReviewHistory } from '../utils'; -export interface HotspotViewerReviewHistoryTabProps { - history: ReviewHistoryElement[]; +export interface HotspotReviewHistoryProps { hotspot: Hotspot; - onUpdateHotspot: () => void; } -export default function HotspotViewerReviewHistoryTab(props: HotspotViewerReviewHistoryTabProps) { - const { history, hotspot } = props; +export default function HotspotReviewHistory(props: HotspotReviewHistoryProps) { + const reviewHistory = getHotspotReviewHistory(props.hotspot); return ( - <div className="padded"> - {history.map((elt, historyIndex) => ( - <React.Fragment key={historyIndex}> - {historyIndex > 0 && <hr />} - <div className="padded"> + <> + {reviewHistory.map((historyElt, historyIndex) => { + const { user, type, diffs, date, html } = historyElt; + return ( + <div + className={classNames('padded', { 'bordered-top': historyIndex > 0 })} + key={historyIndex}> <div className="display-flex-center"> - {elt.user.name && ( + {user.name && ( <> <Avatar className="little-spacer-right" - hash={elt.user.avatar} - name={elt.user.name} + hash={user.avatar} + name={user.name} size={20} /> <strong> - {elt.user.active - ? elt.user.name - : translateWithParameters('user.x_deleted', elt.user.name)} + {user.active ? user.name : translateWithParameters('user.x_deleted', user.name)} </strong> - {elt.type === ReviewHistoryType.Creation && ( + {type === ReviewHistoryType.Creation && ( <span className="little-spacer-left"> - {translate('hotspots.tabs.review_history.created')} + {translate('hotspots.review_history.created')} </span> )} - {elt.type === ReviewHistoryType.Comment && ( + {type === ReviewHistoryType.Comment && ( <span className="little-spacer-left"> - {translate('hotspots.tabs.review_history.comment.added')} + {translate('hotspots.review_history.comment_added')} </span> )} <span className="little-spacer-left little-spacer-right">-</span> </> )} - <DateTimeFormatter date={elt.date} /> + <DateTimeFormatter date={date} /> </div> - {elt.type === ReviewHistoryType.Diff && elt.diffs && ( + {type === ReviewHistoryType.Diff && diffs && ( <div className="spacer-top"> - {elt.diffs.map((diff, diffIndex) => ( + {diffs.map((diff, diffIndex) => ( <IssueChangelogDiff diff={diff} key={diffIndex} /> ))} </div> )} - {elt.type === ReviewHistoryType.Comment && elt.html && ( + {type === ReviewHistoryType.Comment && html && ( <div className="spacer-top markdown" - dangerouslySetInnerHTML={{ __html: sanitize(elt.html) }} + dangerouslySetInnerHTML={{ __html: sanitize(html) }} /> )} </div> - </React.Fragment> - ))} - <hr /> - <HotspotViewerReviewHistoryTabCommentBox - hotspot={hotspot} - onUpdateHotspot={props.onUpdateHotspot} - /> - </div> + ); + })} + </> ); } 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 new file mode 100644 index 00000000000..c7d73b89a6b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistoryAndComments.tsx @@ -0,0 +1,131 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 classNames from 'classnames'; +import * as React from 'react'; +import { Button, ResetButtonLink } from 'sonar-ui-common/components/controls/buttons'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import { commentSecurityHotspot } from '../../../api/security-hotspots'; +import MarkdownTips from '../../../components/common/MarkdownTips'; +import { isLoggedIn } from '../../../helpers/users'; +import { Hotspot } from '../../../types/security-hotspots'; +import HotspotReviewHistory from './HotspotReviewHistory'; + +interface Props { + currentUser: T.CurrentUser; + hotspot: Hotspot; + commentTextRef: React.RefObject<HTMLTextAreaElement>; + commentVisible: boolean; + onCommentUpdate: () => void; + onOpenComment: () => void; + onCloseComment: () => void; +} + +interface State { + comment: string; +} + +export default class HotspotReviewHistoryAndComments extends React.PureComponent<Props, State> { + constructor(props: Props) { + super(props); + this.state = { + comment: '' + }; + } + + componentDidUpdate(prevProps: Props) { + if (prevProps.hotspot !== this.props.hotspot) { + this.setState({ + comment: '' + }); + } + } + + handleCommentChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => { + 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(); + }); + }; + + render() { + const { currentUser, hotspot, commentTextRef, commentVisible } = this.props; + const { comment } = this.state; + return ( + <> + <h1>{translate('hotspot.section.activity')}</h1> + <div className="padded"> + <HotspotReviewHistory hotspot={hotspot} /> + + {isLoggedIn(currentUser) && ( + <> + <hr /> + <div className="big-spacer-top"> + <Button + className={classNames({ invisible: commentVisible })} + id="hotspot-comment-box-display" + onClick={this.props.onOpenComment}> + {translate('hotspots.comment.open')} + </Button> + + <div className={classNames({ invisible: !commentVisible })}> + <div className="little-spacer-bottom">{translate('hotspots.comment.field')}</div> + <textarea + className="form-field fixed-width width-100 spacer-bottom" + onChange={this.handleCommentChange} + ref={commentTextRef} + rows={2} + value={comment} + /> + <div className="display-flex-space-between display-flex-center "> + <MarkdownTips className="huge-spacer-bottom" /> + <div> + <Button + className="huge-spacer-bottom" + id="hotspot-comment-box-submit" + onClick={this.handleSubmitComment}> + {translate('hotspots.comment.submit')} + </Button> + <ResetButtonLink + className="spacer-left huge-spacer-bottom" + id="hotspot-comment-box-cancel" + onClick={this.handleCloseComment}> + {translate('cancel')} + </ResetButtonLink> + </div> + </div> + </div> + </div> + </> + )} + </div> + </> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx index 2a23816f1ba..58a2789fdef 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx @@ -19,6 +19,7 @@ */ import * as React from 'react'; +import { scrollToElement } from 'sonar-ui-common/helpers/scrolling'; import { getSecurityHotspotDetails } from '../../../api/security-hotspots'; import { BranchLike } from '../../../types/branch-like'; import { Hotspot } from '../../../types/security-hotspots'; @@ -35,21 +36,34 @@ interface Props { interface State { hotspot?: Hotspot; loading: boolean; + commentVisible: boolean; } export default class HotspotViewer extends React.PureComponent<Props, State> { mounted = false; - state: State = { loading: false }; + state: State; + commentTextRef: React.RefObject<HTMLTextAreaElement>; + parentScrollRef: React.RefObject<HTMLDivElement>; + + constructor(props: Props) { + super(props); + this.commentTextRef = React.createRef<HTMLTextAreaElement>(); + this.parentScrollRef = React.createRef<HTMLDivElement>(); + this.state = { loading: false, commentVisible: false }; + } componentDidMount() { this.mounted = true; this.fetchHotspot(); } - componentDidUpdate(prevProps: Props) { + componentDidUpdate(prevProps: Props, prevState: State) { if (prevProps.hotspotKey !== this.props.hotspotKey) { this.fetchHotspot(); } + if (this.commentTextRef.current && !prevState.commentVisible && this.state.commentVisible) { + this.commentTextRef.current.focus({ preventScroll: true }); + } } componentWillUnmount() { @@ -76,17 +90,40 @@ export default class HotspotViewer extends React.PureComponent<Props, State> { }); }; + handleOpenComment = () => { + this.setState({ commentVisible: true }); + if (this.commentTextRef.current) { + // Edge case when the comment is already open and unfocus. + this.commentTextRef.current.focus({ preventScroll: true }); + } + if (this.commentTextRef.current && this.parentScrollRef.current) { + scrollToElement(this.commentTextRef.current, { + parent: this.parentScrollRef.current, + bottomOffset: 100 + }); + } + }; + + handleCloseComment = () => { + this.setState({ commentVisible: false }); + }; + render() { const { branchLike, component, securityCategories } = this.props; - const { hotspot, loading } = this.state; + const { hotspot, loading, commentVisible } = this.state; return ( <HotspotViewerRenderer branchLike={branchLike} component={component} + commentTextRef={this.commentTextRef} + commentVisible={commentVisible} hotspot={hotspot} loading={loading} + onCloseComment={this.handleCloseComment} + onOpenComment={this.handleOpenComment} onUpdateHotspot={this.handleHotspotUpdate} + parentScrollRef={this.parentScrollRef} securityCategories={securityCategories} /> ); diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx index 0730dd96b48..db0db20080f 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx @@ -19,16 +19,20 @@ */ import * as classNames from 'classnames'; import * as React from 'react'; +import { Button } from 'sonar-ui-common/components/controls/buttons'; import { ClipboardButton } from 'sonar-ui-common/components/controls/clipboard'; import LinkIcon from 'sonar-ui-common/components/icons/LinkIcon'; import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; import { translate } from 'sonar-ui-common/helpers/l10n'; import { getPathUrlAsString } from 'sonar-ui-common/helpers/urls'; +import { withCurrentUser } from '../../../components/hoc/withCurrentUser'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { getComponentSecurityHotspotsUrl } from '../../../helpers/urls'; +import { isLoggedIn } from '../../../helpers/users'; import { BranchLike } from '../../../types/branch-like'; import { Hotspot } from '../../../types/security-hotspots'; import Assignee from './assignee/Assignee'; +import HotspotReviewHistoryAndComments from './HotspotReviewHistoryAndComments'; import HotspotSnippetContainer from './HotspotSnippetContainer'; import './HotspotViewer.css'; import HotspotViewerTabs from './HotspotViewerTabs'; @@ -37,14 +41,30 @@ import Status from './status/Status'; export interface HotspotViewerRendererProps { branchLike?: BranchLike; component: T.Component; + currentUser: T.CurrentUser; hotspot?: Hotspot; loading: boolean; + commentVisible: boolean; + commentTextRef: React.RefObject<HTMLTextAreaElement>; + onOpenComment: () => void; + onCloseComment: () => void; onUpdateHotspot: () => Promise<void>; + parentScrollRef: React.RefObject<HTMLDivElement>; securityCategories: T.StandardSecurityCategories; } -export default function HotspotViewerRenderer(props: HotspotViewerRendererProps) { - const { branchLike, component, hotspot, loading, securityCategories } = props; +export function HotspotViewerRenderer(props: HotspotViewerRendererProps) { + const { + branchLike, + component, + currentUser, + hotspot, + loading, + securityCategories, + commentTextRef, + commentVisible, + parentScrollRef + } = props; const permalink = getPathUrlAsString( getComponentSecurityHotspotsUrl(component.key, { @@ -57,13 +77,22 @@ export default function HotspotViewerRenderer(props: HotspotViewerRendererProps) return ( <DeferredSpinner loading={loading}> {hotspot && ( - <div className="big-padded"> + <div className="big-padded hotspot-content" ref={parentScrollRef}> <div className="huge-spacer-bottom display-flex-space-between"> <strong className="big big-spacer-right">{hotspot.message}</strong> - <ClipboardButton copyValue={permalink}> - <LinkIcon className="spacer-right" /> - <span>{translate('hotspots.get_permalink')}</span> - </ClipboardButton> + <div className="display-flex-row flex-0"> + {isLoggedIn(currentUser) && ( + <div className="dropdown spacer-right flex-1-0-auto"> + <Button onClick={props.onOpenComment}> + {translate('hotspots.comment.open')} + </Button> + </div> + )} + <ClipboardButton className="flex-1-0-auto" copyValue={permalink}> + <LinkIcon className="spacer-right" /> + <span>{translate('hotspots.get_permalink')}</span> + </ClipboardButton> + </div> </div> <div className="huge-spacer-bottom display-flex-row"> @@ -97,9 +126,20 @@ export default function HotspotViewerRenderer(props: HotspotViewerRendererProps) </div> <HotspotSnippetContainer branchLike={branchLike} hotspot={hotspot} /> - <HotspotViewerTabs hotspot={hotspot} onUpdateHotspot={props.onUpdateHotspot} /> + <HotspotViewerTabs hotspot={hotspot} /> + <HotspotReviewHistoryAndComments + commentTextRef={commentTextRef} + commentVisible={commentVisible} + currentUser={currentUser} + hotspot={hotspot} + onCloseComment={props.onCloseComment} + onCommentUpdate={props.onUpdateHotspot} + onOpenComment={props.onOpenComment} + /> </div> )} </DeferredSpinner> ); } + +export default withCurrentUser(HotspotViewerRenderer); diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerReviewHistoryTabCommentBox.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerReviewHistoryTabCommentBox.tsx deleted file mode 100644 index df8afcfff29..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerReviewHistoryTabCommentBox.tsx +++ /dev/null @@ -1,89 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 { Button, ResetButtonLink } from 'sonar-ui-common/components/controls/buttons'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { commentSecurityHotspot } from '../../../api/security-hotspots'; -import MarkdownTips from '../../../components/common/MarkdownTips'; -import { Hotspot } from '../../../types/security-hotspots'; - -export interface HotspotViewerReviewHistoryTabCommentBoxProps { - hotspot: Hotspot; - onUpdateHotspot: () => void; -} - -export default function HotspotViewerReviewHistoryTabCommentBox( - props: HotspotViewerReviewHistoryTabCommentBoxProps -) { - const { hotspot } = props; - const [comment, setComment] = React.useState(); - const [isCommentBoxVisible, setCommentBoxVisibility] = React.useState(false); - - const onCancel = () => { - setComment(null); - setCommentBoxVisibility(false); - }; - - const onComment = () => { - return commentSecurityHotspot(hotspot.key, comment).then(() => { - onCancel(); - props.onUpdateHotspot(); - }); - }; - - return ( - <div className="big-spacer-top"> - {!isCommentBoxVisible ? ( - <Button id="hotspot-comment-box-display" onClick={() => setCommentBoxVisibility(true)}> - {translate('hotspots.tabs.review_history.comment.add')} - </Button> - ) : ( - <> - <div className="little-spacer-bottom"> - {translate('hotspots.tabs.review_history.comment.field')} - </div> - <textarea - autoFocus={true} - className="form-field fixed-width width-100 spacer-bottom" - onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) => - setComment(event.target.value) - } - rows={2} - /> - <div className="display-flex-space-between display-flex-center"> - <MarkdownTips /> - <div> - <Button id="hotspot-comment-box-submit" onClick={onComment}> - {translate('hotspots.tabs.review_history.comment.submit')} - </Button> - <ResetButtonLink - className="spacer-left" - id="hotspot-comment-box-cancel" - onClick={onCancel}> - {translate('cancel')} - </ResetButtonLink> - </div> - </div> - </> - )} - </div> - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx index 961c8829c64..69297641ea7 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx @@ -23,12 +23,9 @@ import BoxedTabs from 'sonar-ui-common/components/controls/BoxedTabs'; import Tab from 'sonar-ui-common/components/controls/Tabs'; import { translate } from 'sonar-ui-common/helpers/l10n'; import { Hotspot } from '../../../types/security-hotspots'; -import { getHotspotReviewHistory } from '../utils'; -import HotspotViewerReviewHistoryTab from './HotspotViewerReviewHistoryTab'; interface Props { hotspot: Hotspot; - onUpdateHotspot: () => void; } interface State { @@ -39,14 +36,13 @@ interface State { interface Tab { key: TabKeys; label: React.ReactNode; - content: React.ReactNode; + content: string; } export enum TabKeys { RiskDescription = 'risk', VulnerabilityDescription = 'vulnerability', - FixRecommendation = 'fix', - ReviewHistory = 'review' + FixRecommendation = 'fix' } export default class HotspotViewerTabs extends React.PureComponent<Props, State> { @@ -77,7 +73,6 @@ export default class HotspotViewerTabs extends React.PureComponent<Props, State> computeTabs() { const { hotspot } = this.props; - const hotspotReviewHistory = getHotspotReviewHistory(hotspot); return [ { key: TabKeys.RiskDescription, @@ -93,26 +88,6 @@ export default class HotspotViewerTabs extends React.PureComponent<Props, State> key: TabKeys.FixRecommendation, label: translate('hotspots.tabs.fix_recommendations'), content: hotspot.rule.fixRecommendations || '' - }, - { - key: TabKeys.ReviewHistory, - label: ( - <> - <span>{translate('hotspots.tabs.review_history')}</span> - {hotspotReviewHistory.functionalCount > 0 && ( - <span className="counter-badge spacer-left"> - {hotspotReviewHistory.functionalCount} - </span> - )} - </> - ), - content: hotspotReviewHistory.history.length > 0 && ( - <HotspotViewerReviewHistoryTab - history={hotspotReviewHistory.history} - hotspot={hotspot} - onUpdateHotspot={this.props.onUpdateHotspot} - /> - ) } ].filter(tab => Boolean(tab.content)); } @@ -127,14 +102,10 @@ export default class HotspotViewerTabs extends React.PureComponent<Props, State> <> <BoxedTabs onSelect={this.handleSelectTabs} selected={currentTab.key} tabs={tabs} /> <div className="bordered huge-spacer-bottom"> - {typeof currentTab.content === 'string' ? ( - <div - className="markdown big-padded" - dangerouslySetInnerHTML={{ __html: sanitize(currentTab.content) }} - /> - ) : ( - <>{currentTab.content}</> - )} + <div + className="markdown big-padded" + dangerouslySetInnerHTML={{ __html: sanitize(currentTab.content) }} + /> </div> </> ); diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistory-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistory-test.tsx new file mode 100644 index 00000000000..1488c45d1c9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistory-test.tsx @@ -0,0 +1,69 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { mockHotspot } from '../../../../helpers/mocks/security-hotspots'; +import { mockUser } from '../../../../helpers/testMocks'; +import HotspotReviewHistory, { HotspotReviewHistoryProps } from '../HotspotReviewHistory'; + +it('should render correctly', () => { + const wrapper = shallowRender(); + expect(wrapper).toMatchSnapshot(); +}); + +function shallowRender(props?: Partial<HotspotReviewHistoryProps>) { + const changelogElement: T.IssueChangelog = { + creationDate: '2018-10-01', + isUserActive: true, + user: 'me', + userName: 'me-name', + diffs: [ + { + key: 'assign', + newValue: 'me', + oldValue: 'him' + } + ] + }; + const commentElement = { + key: 'comment-1', + createdAt: '2018-09-10', + htmlText: '<strong>TEST</strong>', + markdown: '*TEST*', + updatable: true, + login: 'dude-1', + user: mockUser({ login: 'dude-1' }) + }; + const commentElement1 = { + key: 'comment-2', + createdAt: '2018-09-11', + htmlText: '<strong>TEST</strong>', + markdown: '*TEST*', + updatable: true, + login: 'dude-2', + user: mockUser({ login: 'dude-2' }) + }; + const hotspot = mockHotspot({ + creationDate: '2018-09-01', + changelog: [changelogElement], + comment: [commentElement, commentElement1] + }); + return shallow(<HotspotReviewHistory hotspot={hotspot} {...props} />); +} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistoryAndComments-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistoryAndComments-test.tsx new file mode 100644 index 00000000000..f1d78b6c18a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistoryAndComments-test.tsx @@ -0,0 +1,111 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; +import { commentSecurityHotspot } from '../../../../api/security-hotspots'; +import { mockHotspot } from '../../../../helpers/mocks/security-hotspots'; +import { mockCurrentUser } from '../../../../helpers/testMocks'; +import { isLoggedIn } from '../../../../helpers/users'; +import HotspotReviewHistoryAndComments from '../HotspotReviewHistoryAndComments'; + +jest.mock('../../../../api/security-hotspots', () => ({ + commentSecurityHotspot: jest.fn().mockResolvedValue({}) +})); + +jest.mock('../../../../helpers/users', () => ({ isLoggedIn: jest.fn(() => true) })); + +it('should render correctly', () => { + const wrapper = shallowRender(); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render correctly without user', () => { + ((isLoggedIn as any) as jest.Mock<boolean, [boolean]>).mockReturnValueOnce(false); + const wrapper = shallowRender(); + expect(wrapper).toMatchSnapshot(); +}); + +it('should open comment form', () => { + const wrapper = shallowRender(); + wrapper.find('#hotspot-comment-box-display').simulate('click'); + expect(wrapper.instance().props.onOpenComment).toHaveBeenCalled(); +}); + +it('should submit comment', async () => { + const mockApi = commentSecurityHotspot as jest.Mock; + const hotspot = mockHotspot(); + const wrapper = shallowRender({ hotspot, commentVisible: true }); + mockApi.mockClear(); + wrapper.instance().setState({ comment: 'Comment' }); + + wrapper.find('#hotspot-comment-box-submit').simulate('click'); + await waitAndUpdate(wrapper); + + expect(mockApi).toHaveBeenCalledWith(hotspot.key, 'Comment'); + expect(wrapper.state().comment).toBe(''); + expect(wrapper.instance().props.onCloseComment).toHaveBeenCalledTimes(1); + expect(wrapper.instance().props.onCommentUpdate).toHaveBeenCalledTimes(1); +}); + +it('should cancel comment', () => { + const mockApi = commentSecurityHotspot as jest.Mock; + const hotspot = mockHotspot(); + const wrapper = shallowRender({ hotspot, commentVisible: true }); + wrapper.instance().setState({ comment: 'Comment' }); + mockApi.mockClear(); + + wrapper.find('#hotspot-comment-box-cancel').simulate('click'); + + expect(mockApi).not.toHaveBeenCalled(); + expect(wrapper.state().comment).toBe(''); + expect(wrapper.instance().props.onCloseComment).toHaveBeenCalledTimes(1); +}); + +it('should change comment', () => { + const wrapper = shallowRender({ commentVisible: true }); + wrapper.instance().setState({ comment: 'Comment' }); + wrapper.find('textarea').simulate('change', { target: { value: 'Foo' } }); + + expect(wrapper.state().comment).toBe('Foo'); +}); + +it('should reset on change hotspot', () => { + const wrapper = shallowRender(); + wrapper.setState({ comment: 'NOP' }); + wrapper.setProps({ hotspot: mockHotspot() }); + + expect(wrapper.state().comment).toBe(''); +}); + +function shallowRender(props?: Partial<HotspotReviewHistoryAndComments['props']>) { + return shallow<HotspotReviewHistoryAndComments>( + <HotspotReviewHistoryAndComments + commentTextRef={React.createRef()} + commentVisible={false} + currentUser={mockCurrentUser()} + hotspot={mockHotspot()} + onCloseComment={jest.fn()} + onCommentUpdate={jest.fn()} + onOpenComment={jest.fn()} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewer-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewer-test.tsx index 65efeea8fca..5fce1d9f64b 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewer-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewer-test.tsx @@ -18,11 +18,14 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { shallow } from 'enzyme'; +import { clone } from 'lodash'; import * as React from 'react'; +import { scrollToElement } from 'sonar-ui-common/helpers/scrolling'; import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; import { getSecurityHotspotDetails } from '../../../../api/security-hotspots'; import { mockComponent } from '../../../../helpers/testMocks'; import HotspotViewer from '../HotspotViewer'; +import HotspotViewerRenderer from '../HotspotViewerRenderer'; const hotspotKey = 'hotspot-key'; @@ -30,6 +33,8 @@ jest.mock('../../../../api/security-hotspots', () => ({ getSecurityHotspotDetails: jest.fn().mockResolvedValue({ id: `I am a detailled hotspot` }) })); +jest.mock('sonar-ui-common/helpers/scrolling', () => ({ scrollToElement: jest.fn() })); + it('should render correctly', async () => { const wrapper = shallowRender(); expect(wrapper).toMatchSnapshot(); @@ -46,6 +51,56 @@ it('should render correctly', async () => { expect(getSecurityHotspotDetails).toHaveBeenCalledWith(newHotspotKey); }); +it('should update refresh hotspot on update', () => { + const wrapper = shallowRender(); + const mockGetHostpot = getSecurityHotspotDetails as jest.Mock; + mockGetHostpot.mockClear(); + wrapper.find(HotspotViewerRenderer).simulate('updateHotspot'); + expect(mockGetHostpot).toHaveBeenCalledTimes(1); +}); + +it('should open comment form when scroll to comment', () => { + const wrapper = shallowRender(); + const mockTextRef = ({ current: { focus: jest.fn() } } as any) as React.RefObject< + HTMLTextAreaElement + >; + const mockParentRef = ({ current: {} } as any) as React.RefObject<HTMLDivElement>; + wrapper.instance().parentScrollRef = mockParentRef; + wrapper.instance().commentTextRef = mockTextRef; + + wrapper.find(HotspotViewerRenderer).simulate('openComment'); + + expect(wrapper.state().commentVisible).toBe(true); + expect(mockTextRef.current?.focus).toHaveBeenCalled(); + expect(scrollToElement).toHaveBeenCalledWith(mockTextRef.current, expect.anything()); +}); + +it('should close comment', () => { + const wrapper = shallowRender(); + wrapper.setState({ commentVisible: true }); + wrapper.find(HotspotViewerRenderer).simulate('closeComment'); + expect(wrapper.state().commentVisible).toBe(false); +}); + +it('should reset loading even on fetch error', async () => { + const mockGetHostpot = getSecurityHotspotDetails as jest.Mock; + mockGetHostpot.mockRejectedValueOnce({}); + + const wrapper = shallowRender(); + await waitAndUpdate(wrapper); + + expect(wrapper.state().loading).toBe(false); +}); + +it('should keep state on unmoint', () => { + const wrapper = shallowRender(); + wrapper.instance().componentWillUnmount(); + const prevState = clone(wrapper.state()); + + wrapper.find(HotspotViewerRenderer).simulate('updateHotspot'); + expect(wrapper.state()).toStrictEqual(prevState); +}); + function shallowRender(props?: Partial<HotspotViewer['props']>) { return shallow<HotspotViewer>( <HotspotViewer diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerRenderer-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerRenderer-test.tsx index 5fb5a19fc4d..f9207838319 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerRenderer-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerRenderer-test.tsx @@ -21,8 +21,10 @@ import { shallow } from 'enzyme'; import * as React from 'react'; import { mockBranch } from '../../../../helpers/mocks/branch-like'; import { mockHotspot } from '../../../../helpers/mocks/security-hotspots'; -import { mockComponent, mockUser } from '../../../../helpers/testMocks'; -import HotspotViewerRenderer, { HotspotViewerRendererProps } from '../HotspotViewerRenderer'; +import { mockComponent, mockCurrentUser, mockUser } from '../../../../helpers/testMocks'; +import { HotspotViewerRenderer, HotspotViewerRendererProps } from '../HotspotViewerRenderer'; + +jest.mock('../../../../helpers/users', () => ({ isLoggedIn: jest.fn(() => true) })); it('should render correctly', () => { const wrapper = shallowRender(); @@ -49,9 +51,15 @@ function shallowRender(props?: Partial<HotspotViewerRendererProps>) { <HotspotViewerRenderer branchLike={mockBranch()} component={mockComponent()} + commentTextRef={React.createRef()} + commentVisible={false} + currentUser={mockCurrentUser()} hotspot={mockHotspot()} loading={false} + onCloseComment={jest.fn()} + onOpenComment={jest.fn()} onUpdateHotspot={jest.fn()} + parentScrollRef={React.createRef()} securityCategories={{ 'sql-injection': { title: 'SQL injection' } }} {...props} /> diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerReviewHistoryTab-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerReviewHistoryTab-test.tsx deleted file mode 100644 index 48e790c2e44..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerReviewHistoryTab-test.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 { - mockHotspot, - mockHotspotReviewHistoryElement -} from '../../../../helpers/mocks/security-hotspots'; -import { mockUser } from '../../../../helpers/testMocks'; -import { ReviewHistoryType } from '../../../../types/security-hotspots'; -import HotspotViewerReviewHistoryTab, { - HotspotViewerReviewHistoryTabProps -} from '../HotspotViewerReviewHistoryTab'; -import HotspotViewerReviewHistoryTabCommentBox from '../HotspotViewerReviewHistoryTabCommentBox'; - -it('should render correctly', () => { - const wrapper = shallowRender(); - expect(wrapper).toMatchSnapshot(); -}); - -it('should propagate onHotspotUpdate correctly', () => { - const onUpdateHotspot = jest.fn(); - const wrapper = shallowRender({ onUpdateHotspot }); - - wrapper - .find(HotspotViewerReviewHistoryTabCommentBox) - .props() - .onUpdateHotspot(); - expect(onUpdateHotspot).toHaveBeenCalled(); -}); - -function shallowRender(props?: Partial<HotspotViewerReviewHistoryTabProps>) { - return shallow<HotspotViewerReviewHistoryTabProps>( - <HotspotViewerReviewHistoryTab - history={[ - mockHotspotReviewHistoryElement({ user: mockUser({ avatar: 'with-avatar' }) }), - mockHotspotReviewHistoryElement({ user: mockUser({ active: false }) }), - mockHotspotReviewHistoryElement({ user: mockUser({ login: undefined, name: undefined }) }), - mockHotspotReviewHistoryElement({ - type: ReviewHistoryType.Diff, - diffs: [ - { key: 'test', oldValue: 'old', newValue: 'new' }, - { key: 'test-1', oldValue: 'old-1', newValue: 'new-1' } - ] - }), - mockHotspotReviewHistoryElement({ - type: ReviewHistoryType.Comment, - html: '<strong>bold text</strong>' - }) - ]} - hotspot={mockHotspot()} - onUpdateHotspot={jest.fn()} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerReviewHistoryTabCommentBox-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerReviewHistoryTabCommentBox-test.tsx deleted file mode 100644 index febd2a6a017..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerReviewHistoryTabCommentBox-test.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; -import { commentSecurityHotspot } from '../../../../api/security-hotspots'; -import { mockHotspot } from '../../../../helpers/mocks/security-hotspots'; -import HotspotViewerReviewHistoryTabCommentBox, { - HotspotViewerReviewHistoryTabCommentBoxProps -} from '../HotspotViewerReviewHistoryTabCommentBox'; - -jest.mock('../../../../api/security-hotspots', () => ({ - commentSecurityHotspot: jest.fn().mockResolvedValue(null) -})); - -it('should render correctly', () => { - const wrapper = shallowRender(); - expect(wrapper).toMatchSnapshot(); - - // Show the comment box - wrapper.find('#hotspot-comment-box-display').simulate('click'); - expect(wrapper).toMatchSnapshot('with comment box'); - - // Cancel comment - wrapper.find('#hotspot-comment-box-cancel').simulate('click'); - expect(wrapper).toMatchSnapshot('without comment box'); -}); - -it('should properly submit a comment', async () => { - const hotspot = mockHotspot(); - const onUpdateHotspot = jest.fn(); - const wrapper = shallowRender({ hotspot, onUpdateHotspot }); - - wrapper.find('#hotspot-comment-box-display').simulate('click'); - wrapper.find('textarea').simulate('change', { target: { value: 'tata' } }); - wrapper.find('#hotspot-comment-box-submit').simulate('click'); - - await waitAndUpdate(wrapper); - - expect(commentSecurityHotspot).toHaveBeenCalledWith(hotspot.key, 'tata'); - expect(onUpdateHotspot).toHaveBeenCalled(); - - expect(wrapper).toMatchSnapshot('without comment box'); -}); - -function shallowRender(props?: Partial<HotspotViewerReviewHistoryTabCommentBoxProps>) { - return shallow<HotspotViewerReviewHistoryTabCommentBoxProps>( - <HotspotViewerReviewHistoryTabCommentBox - hotspot={mockHotspot()} - onUpdateHotspot={jest.fn()} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerTabs-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerTabs-test.tsx index 24a09807b6e..aae1722a0ef 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerTabs-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerTabs-test.tsx @@ -22,7 +22,6 @@ import * as React from 'react'; import BoxedTabs from 'sonar-ui-common/components/controls/BoxedTabs'; import { mockHotspot, mockHotspotRule } from '../../../../helpers/mocks/security-hotspots'; import { mockUser } from '../../../../helpers/testMocks'; -import HotspotViewerReviewHistoryTab from '../HotspotViewerReviewHistoryTab'; import HotspotViewerTabs, { TabKeys } from '../HotspotViewerTabs'; it('should render correctly', () => { @@ -37,9 +36,6 @@ it('should render correctly', () => { onSelect(TabKeys.FixRecommendation); expect(wrapper).toMatchSnapshot('fix'); - onSelect(TabKeys.ReviewHistory); - expect(wrapper).toMatchSnapshot('review'); - expect( shallowRender({ hotspot: mockHotspot({ @@ -88,31 +84,16 @@ it('should filter empty tab', () => { ).toBe(count - 1); }); -it('should propagate onHotspotUpdate correctly', () => { - const onUpdateHotspot = jest.fn(); - const wrapper = shallowRender({ onUpdateHotspot }); - const onSelect = wrapper.find(BoxedTabs).prop('onSelect') as (tab: TabKeys) => void; - - onSelect(TabKeys.ReviewHistory); - wrapper - .find(HotspotViewerReviewHistoryTab) - .props() - .onUpdateHotspot(); - expect(onUpdateHotspot).toHaveBeenCalled(); -}); - it('should select first tab on hotspot update', () => { const wrapper = shallowRender(); const onSelect = wrapper.find(BoxedTabs).prop('onSelect') as (tab: TabKeys) => void; - onSelect(TabKeys.ReviewHistory); - expect(wrapper.state().currentTab.key).toBe(TabKeys.ReviewHistory); + onSelect(TabKeys.VulnerabilityDescription); + expect(wrapper.state().currentTab.key).toBe(TabKeys.VulnerabilityDescription); wrapper.setProps({ hotspot: mockHotspot({ key: 'new_key' }) }); expect(wrapper.state().currentTab.key).toBe(TabKeys.RiskDescription); }); function shallowRender(props?: Partial<HotspotViewerTabs['props']>) { - return shallow<HotspotViewerTabs>( - <HotspotViewerTabs hotspot={mockHotspot()} onUpdateHotspot={jest.fn()} {...props} /> - ); + return shallow<HotspotViewerTabs>(<HotspotViewerTabs hotspot={mockHotspot()} {...props} />); } diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistory-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistory-test.tsx.snap new file mode 100644 index 00000000000..395669fefb7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistory-test.tsx.snap @@ -0,0 +1,151 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<Fragment> + <div + className="padded" + key="0" + > + <div + className="display-flex-center" + > + <Connect(Avatar) + className="little-spacer-right" + name="John Doe" + size={20} + /> + <strong> + John Doe + </strong> + <span + className="little-spacer-left" + > + hotspots.review_history.created + </span> + <span + className="little-spacer-left little-spacer-right" + > + - + </span> + <DateTimeFormatter + date="2018-09-01" + /> + </div> + </div> + <div + className="padded bordered-top" + key="1" + > + <div + className="display-flex-center" + > + <Connect(Avatar) + className="little-spacer-right" + name="John Doe" + size={20} + /> + <strong> + John Doe + </strong> + <span + className="little-spacer-left" + > + hotspots.review_history.comment_added + </span> + <span + className="little-spacer-left little-spacer-right" + > + - + </span> + <DateTimeFormatter + date="2018-09-10" + /> + </div> + <div + className="spacer-top markdown" + dangerouslySetInnerHTML={ + Object { + "__html": "<strong>TEST</strong>", + } + } + /> + </div> + <div + className="padded bordered-top" + key="2" + > + <div + className="display-flex-center" + > + <Connect(Avatar) + className="little-spacer-right" + name="John Doe" + size={20} + /> + <strong> + John Doe + </strong> + <span + className="little-spacer-left" + > + hotspots.review_history.comment_added + </span> + <span + className="little-spacer-left little-spacer-right" + > + - + </span> + <DateTimeFormatter + date="2018-09-11" + /> + </div> + <div + className="spacer-top markdown" + dangerouslySetInnerHTML={ + Object { + "__html": "<strong>TEST</strong>", + } + } + /> + </div> + <div + className="padded bordered-top" + key="3" + > + <div + className="display-flex-center" + > + <Connect(Avatar) + className="little-spacer-right" + name="me-name" + size={20} + /> + <strong> + me-name + </strong> + <span + className="little-spacer-left little-spacer-right" + > + - + </span> + <DateTimeFormatter + date="2018-10-01" + /> + </div> + <div + className="spacer-top" + > + <IssueChangelogDiff + diff={ + Object { + "key": "assign", + "newValue": "me", + "oldValue": "him", + } + } + key="0" + /> + </div> + </div> +</Fragment> +`; diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistoryAndComments-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistoryAndComments-test.tsx.snap new file mode 100644 index 00000000000..e71ea8d94c3 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistoryAndComments-test.tsx.snap @@ -0,0 +1,277 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<Fragment> + <h1> + hotspot.section.activity + </h1> + <div + className="padded" + > + <HotspotReviewHistory + hotspot={ + Object { + "assignee": "assignee", + "assigneeUser": Object { + "active": true, + "local": true, + "login": "assignee", + "name": "John Doe", + }, + "author": "author", + "authorUser": Object { + "active": true, + "local": true, + "login": "author", + "name": "John Doe", + }, + "canChangeStatus": true, + "changelog": Array [], + "comment": Array [], + "component": Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "FIL", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + }, + "creationDate": "2013-05-13T17:55:41+0200", + "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", + "line": 142, + "message": "'3' is a magic number.", + "project": Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "TRK", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + }, + "resolution": "FIXED", + "rule": Object { + "fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", + "key": "squid:S2077", + "name": "That rule", + "riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", + "securityCategory": "sql-injection", + "vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", + "vulnerabilityProbability": "HIGH", + }, + "status": "REVIEWED", + "textRange": Object { + "endLine": 142, + "endOffset": 83, + "startLine": 142, + "startOffset": 26, + }, + "updateDate": "2013-05-13T17:55:42+0200", + "users": Array [ + Object { + "active": true, + "local": true, + "login": "assignee", + "name": "John Doe", + }, + Object { + "active": true, + "local": true, + "login": "author", + "name": "John Doe", + }, + ], + } + } + /> + <hr /> + <div + className="big-spacer-top" + > + <Button + className="" + id="hotspot-comment-box-display" + onClick={[MockFunction]} + > + hotspots.comment.open + </Button> + <div + className="invisible" + > + <div + className="little-spacer-bottom" + > + hotspots.comment.field + </div> + <textarea + className="form-field fixed-width width-100 spacer-bottom" + onChange={[Function]} + rows={2} + value="" + /> + <div + className="display-flex-space-between display-flex-center " + > + <MarkdownTips + className="huge-spacer-bottom" + /> + <div> + <Button + className="huge-spacer-bottom" + id="hotspot-comment-box-submit" + onClick={[Function]} + > + hotspots.comment.submit + </Button> + <ResetButtonLink + className="spacer-left huge-spacer-bottom" + id="hotspot-comment-box-cancel" + onClick={[Function]} + > + cancel + </ResetButtonLink> + </div> + </div> + </div> + </div> + </div> +</Fragment> +`; + +exports[`should render correctly without user 1`] = ` +<Fragment> + <h1> + hotspot.section.activity + </h1> + <div + className="padded" + > + <HotspotReviewHistory + hotspot={ + Object { + "assignee": "assignee", + "assigneeUser": Object { + "active": true, + "local": true, + "login": "assignee", + "name": "John Doe", + }, + "author": "author", + "authorUser": Object { + "active": true, + "local": true, + "login": "author", + "name": "John Doe", + }, + "canChangeStatus": true, + "changelog": Array [], + "comment": Array [], + "component": Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "FIL", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + }, + "creationDate": "2013-05-13T17:55:41+0200", + "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", + "line": 142, + "message": "'3' is a magic number.", + "project": Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "TRK", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + }, + "resolution": "FIXED", + "rule": Object { + "fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", + "key": "squid:S2077", + "name": "That rule", + "riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", + "securityCategory": "sql-injection", + "vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", + "vulnerabilityProbability": "HIGH", + }, + "status": "REVIEWED", + "textRange": Object { + "endLine": 142, + "endOffset": 83, + "startLine": 142, + "startOffset": 26, + }, + "updateDate": "2013-05-13T17:55:42+0200", + "users": Array [ + Object { + "active": true, + "local": true, + "login": "assignee", + "name": "John Doe", + }, + Object { + "active": true, + "local": true, + "login": "author", + "name": "John Doe", + }, + ], + } + } + /> + </div> +</Fragment> +`; diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewer-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewer-test.tsx.snap index 5168e89398f..40d870f0e77 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewer-test.tsx.snap @@ -1,7 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should render correctly 1`] = ` -<HotspotViewerRenderer +<Connect(withCurrentUser(HotspotViewerRenderer)) + commentTextRef={ + Object { + "current": null, + } + } + commentVisible={false} component={ Object { "breadcrumbs": Array [], @@ -26,7 +32,14 @@ exports[`should render correctly 1`] = ` } } loading={true} + onCloseComment={[Function]} + onOpenComment={[Function]} onUpdateHotspot={[Function]} + parentScrollRef={ + Object { + "current": null, + } + } securityCategories={ Object { "cat1": Object { @@ -38,7 +51,13 @@ exports[`should render correctly 1`] = ` `; exports[`should render correctly 2`] = ` -<HotspotViewerRenderer +<Connect(withCurrentUser(HotspotViewerRenderer)) + commentTextRef={ + Object { + "current": null, + } + } + commentVisible={false} component={ Object { "breadcrumbs": Array [], @@ -68,7 +87,14 @@ exports[`should render correctly 2`] = ` } } loading={false} + onCloseComment={[Function]} + onOpenComment={[Function]} onUpdateHotspot={[Function]} + parentScrollRef={ + Object { + "current": null, + } + } securityCategories={ Object { "cat1": Object { diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap index 7ab41bd333d..cf361d6a27f 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap @@ -6,7 +6,7 @@ exports[`should render correctly 1`] = ` timeout={100} > <div - className="big-padded" + className="big-padded hotspot-content" > <div className="huge-spacer-bottom display-flex-space-between" @@ -16,16 +16,30 @@ exports[`should render correctly 1`] = ` > '3' is a magic number. </strong> - <ClipboardButton - copyValue="http://localhost/security_hotspots?id=my-project&branch=branch-6.7&hotspots=01fc972e-2a3c-433e-bcae-0bd7f88f5123" + <div + className="display-flex-row flex-0" > - <LinkIcon - className="spacer-right" - /> - <span> - hotspots.get_permalink - </span> - </ClipboardButton> + <div + className="dropdown spacer-right flex-1-0-auto" + > + <Button + onClick={[MockFunction]} + > + hotspots.comment.open + </Button> + </div> + <ClipboardButton + className="flex-1-0-auto" + copyValue="http://localhost/security_hotspots?id=my-project&branch=branch-6.7&hotspots=01fc972e-2a3c-433e-bcae-0bd7f88f5123" + > + <LinkIcon + className="spacer-right" + /> + <span> + hotspots.get_permalink + </span> + </ClipboardButton> + </div> </div> <div className="huge-spacer-bottom display-flex-row" @@ -491,7 +505,121 @@ exports[`should render correctly 1`] = ` ], } } - onUpdateHotspot={[MockFunction]} + /> + <HotspotReviewHistoryAndComments + commentTextRef={ + Object { + "current": null, + } + } + commentVisible={false} + currentUser={ + Object { + "isLoggedIn": false, + } + } + hotspot={ + Object { + "assignee": "assignee", + "assigneeUser": Object { + "active": true, + "local": true, + "login": "assignee", + "name": "John Doe", + }, + "author": "author", + "authorUser": Object { + "active": true, + "local": true, + "login": "author", + "name": "John Doe", + }, + "canChangeStatus": true, + "changelog": Array [], + "comment": Array [], + "component": Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "FIL", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + }, + "creationDate": "2013-05-13T17:55:41+0200", + "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", + "line": 142, + "message": "'3' is a magic number.", + "project": Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "TRK", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + }, + "resolution": "FIXED", + "rule": Object { + "fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", + "key": "squid:S2077", + "name": "That rule", + "riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", + "securityCategory": "sql-injection", + "vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", + "vulnerabilityProbability": "HIGH", + }, + "status": "REVIEWED", + "textRange": Object { + "endLine": 142, + "endOffset": 83, + "startLine": 142, + "startOffset": 26, + }, + "updateDate": "2013-05-13T17:55:42+0200", + "users": Array [ + Object { + "active": true, + "local": true, + "login": "assignee", + "name": "John Doe", + }, + Object { + "active": true, + "local": true, + "login": "author", + "name": "John Doe", + }, + ], + } + } + onCloseComment={[MockFunction]} + onCommentUpdate={[MockFunction]} + onOpenComment={[MockFunction]} /> </div> </DeferredSpinner> @@ -503,7 +631,7 @@ exports[`should render correctly: anonymous user 1`] = ` timeout={100} > <div - className="big-padded" + className="big-padded hotspot-content" > <div className="huge-spacer-bottom display-flex-space-between" @@ -513,16 +641,30 @@ exports[`should render correctly: anonymous user 1`] = ` > '3' is a magic number. </strong> - <ClipboardButton - copyValue="http://localhost/security_hotspots?id=my-project&branch=branch-6.7&hotspots=01fc972e-2a3c-433e-bcae-0bd7f88f5123" + <div + className="display-flex-row flex-0" > - <LinkIcon - className="spacer-right" - /> - <span> - hotspots.get_permalink - </span> - </ClipboardButton> + <div + className="dropdown spacer-right flex-1-0-auto" + > + <Button + onClick={[MockFunction]} + > + hotspots.comment.open + </Button> + </div> + <ClipboardButton + className="flex-1-0-auto" + copyValue="http://localhost/security_hotspots?id=my-project&branch=branch-6.7&hotspots=01fc972e-2a3c-433e-bcae-0bd7f88f5123" + > + <LinkIcon + className="spacer-right" + /> + <span> + hotspots.get_permalink + </span> + </ClipboardButton> + </div> </div> <div className="huge-spacer-bottom display-flex-row" @@ -988,7 +1130,121 @@ exports[`should render correctly: anonymous user 1`] = ` ], } } - onUpdateHotspot={[MockFunction]} + /> + <HotspotReviewHistoryAndComments + commentTextRef={ + Object { + "current": null, + } + } + commentVisible={false} + currentUser={ + Object { + "isLoggedIn": false, + } + } + hotspot={ + Object { + "assignee": "assignee", + "assigneeUser": Object { + "active": true, + "local": true, + "login": "assignee", + "name": "John Doe", + }, + "author": "author", + "authorUser": Object { + "active": true, + "local": true, + "login": "author", + "name": "John Doe", + }, + "canChangeStatus": true, + "changelog": Array [], + "comment": Array [], + "component": Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "FIL", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + }, + "creationDate": "2013-05-13T17:55:41+0200", + "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", + "line": 142, + "message": "'3' is a magic number.", + "project": Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "TRK", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + }, + "resolution": "FIXED", + "rule": Object { + "fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", + "key": "squid:S2077", + "name": "That rule", + "riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", + "securityCategory": "sql-injection", + "vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", + "vulnerabilityProbability": "HIGH", + }, + "status": "REVIEWED", + "textRange": Object { + "endLine": 142, + "endOffset": 83, + "startLine": 142, + "startOffset": 26, + }, + "updateDate": "2013-05-13T17:55:42+0200", + "users": Array [ + Object { + "active": true, + "local": true, + "login": "assignee", + "name": "John Doe", + }, + Object { + "active": true, + "local": true, + "login": "author", + "name": "John Doe", + }, + ], + } + } + onCloseComment={[MockFunction]} + onCommentUpdate={[MockFunction]} + onOpenComment={[MockFunction]} /> </div> </DeferredSpinner> @@ -1000,7 +1256,7 @@ exports[`should render correctly: assignee without name 1`] = ` timeout={100} > <div - className="big-padded" + className="big-padded hotspot-content" > <div className="huge-spacer-bottom display-flex-space-between" @@ -1010,16 +1266,30 @@ exports[`should render correctly: assignee without name 1`] = ` > '3' is a magic number. </strong> - <ClipboardButton - copyValue="http://localhost/security_hotspots?id=my-project&branch=branch-6.7&hotspots=01fc972e-2a3c-433e-bcae-0bd7f88f5123" + <div + className="display-flex-row flex-0" > - <LinkIcon - className="spacer-right" - /> - <span> - hotspots.get_permalink - </span> - </ClipboardButton> + <div + className="dropdown spacer-right flex-1-0-auto" + > + <Button + onClick={[MockFunction]} + > + hotspots.comment.open + </Button> + </div> + <ClipboardButton + className="flex-1-0-auto" + copyValue="http://localhost/security_hotspots?id=my-project&branch=branch-6.7&hotspots=01fc972e-2a3c-433e-bcae-0bd7f88f5123" + > + <LinkIcon + className="spacer-right" + /> + <span> + hotspots.get_permalink + </span> + </ClipboardButton> + </div> </div> <div className="huge-spacer-bottom display-flex-row" @@ -1485,7 +1755,121 @@ exports[`should render correctly: assignee without name 1`] = ` ], } } - onUpdateHotspot={[MockFunction]} + /> + <HotspotReviewHistoryAndComments + commentTextRef={ + Object { + "current": null, + } + } + commentVisible={false} + currentUser={ + Object { + "isLoggedIn": false, + } + } + hotspot={ + Object { + "assignee": "assignee", + "assigneeUser": Object { + "active": true, + "local": true, + "login": "assignee_login", + "name": undefined, + }, + "author": "author", + "authorUser": Object { + "active": true, + "local": true, + "login": "author", + "name": "John Doe", + }, + "canChangeStatus": true, + "changelog": Array [], + "comment": Array [], + "component": Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "FIL", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + }, + "creationDate": "2013-05-13T17:55:41+0200", + "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", + "line": 142, + "message": "'3' is a magic number.", + "project": Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "TRK", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + }, + "resolution": "FIXED", + "rule": Object { + "fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", + "key": "squid:S2077", + "name": "That rule", + "riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", + "securityCategory": "sql-injection", + "vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", + "vulnerabilityProbability": "HIGH", + }, + "status": "REVIEWED", + "textRange": Object { + "endLine": 142, + "endOffset": 83, + "startLine": 142, + "startOffset": 26, + }, + "updateDate": "2013-05-13T17:55:42+0200", + "users": Array [ + Object { + "active": true, + "local": true, + "login": "assignee", + "name": "John Doe", + }, + Object { + "active": true, + "local": true, + "login": "author", + "name": "John Doe", + }, + ], + } + } + onCloseComment={[MockFunction]} + onCommentUpdate={[MockFunction]} + onOpenComment={[MockFunction]} /> </div> </DeferredSpinner> @@ -1497,7 +1881,7 @@ exports[`should render correctly: deleted assignee 1`] = ` timeout={100} > <div - className="big-padded" + className="big-padded hotspot-content" > <div className="huge-spacer-bottom display-flex-space-between" @@ -1507,16 +1891,30 @@ exports[`should render correctly: deleted assignee 1`] = ` > '3' is a magic number. </strong> - <ClipboardButton - copyValue="http://localhost/security_hotspots?id=my-project&branch=branch-6.7&hotspots=01fc972e-2a3c-433e-bcae-0bd7f88f5123" + <div + className="display-flex-row flex-0" > - <LinkIcon - className="spacer-right" - /> - <span> - hotspots.get_permalink - </span> - </ClipboardButton> + <div + className="dropdown spacer-right flex-1-0-auto" + > + <Button + onClick={[MockFunction]} + > + hotspots.comment.open + </Button> + </div> + <ClipboardButton + className="flex-1-0-auto" + copyValue="http://localhost/security_hotspots?id=my-project&branch=branch-6.7&hotspots=01fc972e-2a3c-433e-bcae-0bd7f88f5123" + > + <LinkIcon + className="spacer-right" + /> + <span> + hotspots.get_permalink + </span> + </ClipboardButton> + </div> </div> <div className="huge-spacer-bottom display-flex-row" @@ -1982,7 +2380,121 @@ exports[`should render correctly: deleted assignee 1`] = ` ], } } - onUpdateHotspot={[MockFunction]} + /> + <HotspotReviewHistoryAndComments + commentTextRef={ + Object { + "current": null, + } + } + commentVisible={false} + currentUser={ + Object { + "isLoggedIn": false, + } + } + hotspot={ + Object { + "assignee": "assignee", + "assigneeUser": Object { + "active": false, + "local": true, + "login": "john.doe", + "name": "John Doe", + }, + "author": "author", + "authorUser": Object { + "active": true, + "local": true, + "login": "author", + "name": "John Doe", + }, + "canChangeStatus": true, + "changelog": Array [], + "comment": Array [], + "component": Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "FIL", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + }, + "creationDate": "2013-05-13T17:55:41+0200", + "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", + "line": 142, + "message": "'3' is a magic number.", + "project": Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "TRK", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + }, + "resolution": "FIXED", + "rule": Object { + "fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", + "key": "squid:S2077", + "name": "That rule", + "riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", + "securityCategory": "sql-injection", + "vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", + "vulnerabilityProbability": "HIGH", + }, + "status": "REVIEWED", + "textRange": Object { + "endLine": 142, + "endOffset": 83, + "startLine": 142, + "startOffset": 26, + }, + "updateDate": "2013-05-13T17:55:42+0200", + "users": Array [ + Object { + "active": true, + "local": true, + "login": "assignee", + "name": "John Doe", + }, + Object { + "active": true, + "local": true, + "login": "author", + "name": "John Doe", + }, + ], + } + } + onCloseComment={[MockFunction]} + onCommentUpdate={[MockFunction]} + onOpenComment={[MockFunction]} /> </div> </DeferredSpinner> @@ -2001,7 +2513,7 @@ exports[`should render correctly: unassigned 1`] = ` timeout={100} > <div - className="big-padded" + className="big-padded hotspot-content" > <div className="huge-spacer-bottom display-flex-space-between" @@ -2011,16 +2523,30 @@ exports[`should render correctly: unassigned 1`] = ` > '3' is a magic number. </strong> - <ClipboardButton - copyValue="http://localhost/security_hotspots?id=my-project&branch=branch-6.7&hotspots=01fc972e-2a3c-433e-bcae-0bd7f88f5123" + <div + className="display-flex-row flex-0" > - <LinkIcon - className="spacer-right" - /> - <span> - hotspots.get_permalink - </span> - </ClipboardButton> + <div + className="dropdown spacer-right flex-1-0-auto" + > + <Button + onClick={[MockFunction]} + > + hotspots.comment.open + </Button> + </div> + <ClipboardButton + className="flex-1-0-auto" + copyValue="http://localhost/security_hotspots?id=my-project&branch=branch-6.7&hotspots=01fc972e-2a3c-433e-bcae-0bd7f88f5123" + > + <LinkIcon + className="spacer-right" + /> + <span> + hotspots.get_permalink + </span> + </ClipboardButton> + </div> </div> <div className="huge-spacer-bottom display-flex-row" @@ -2486,7 +3012,121 @@ exports[`should render correctly: unassigned 1`] = ` ], } } - onUpdateHotspot={[MockFunction]} + /> + <HotspotReviewHistoryAndComments + commentTextRef={ + Object { + "current": null, + } + } + commentVisible={false} + currentUser={ + Object { + "isLoggedIn": false, + } + } + hotspot={ + Object { + "assignee": undefined, + "assigneeUser": Object { + "active": true, + "local": true, + "login": "assignee", + "name": "John Doe", + }, + "author": "author", + "authorUser": Object { + "active": true, + "local": true, + "login": "author", + "name": "John Doe", + }, + "canChangeStatus": true, + "changelog": Array [], + "comment": Array [], + "component": Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "FIL", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + }, + "creationDate": "2013-05-13T17:55:41+0200", + "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", + "line": 142, + "message": "'3' is a magic number.", + "project": Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "TRK", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + }, + "resolution": "FIXED", + "rule": Object { + "fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", + "key": "squid:S2077", + "name": "That rule", + "riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", + "securityCategory": "sql-injection", + "vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", + "vulnerabilityProbability": "HIGH", + }, + "status": "REVIEWED", + "textRange": Object { + "endLine": 142, + "endOffset": 83, + "startLine": 142, + "startOffset": 26, + }, + "updateDate": "2013-05-13T17:55:42+0200", + "users": Array [ + Object { + "active": true, + "local": true, + "login": "assignee", + "name": "John Doe", + }, + Object { + "active": true, + "local": true, + "login": "author", + "name": "John Doe", + }, + ], + } + } + onCloseComment={[MockFunction]} + onCommentUpdate={[MockFunction]} + onOpenComment={[MockFunction]} /> </div> </DeferredSpinner> diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerReviewHistoryTab-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerReviewHistoryTab-test.tsx.snap deleted file mode 100644 index 3aee5ee3160..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerReviewHistoryTab-test.tsx.snap +++ /dev/null @@ -1,270 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -<div - className="padded" -> - <div - className="padded" - > - <div - className="display-flex-center" - > - <Connect(Avatar) - className="little-spacer-right" - hash="with-avatar" - name="John Doe" - size={20} - /> - <strong> - John Doe - </strong> - <span - className="little-spacer-left" - > - hotspots.tabs.review_history.created - </span> - <span - className="little-spacer-left little-spacer-right" - > - - - </span> - <DateTimeFormatter - date="2019-09-13T17:55:42+0200" - /> - </div> - </div> - <hr /> - <div - className="padded" - > - <div - className="display-flex-center" - > - <Connect(Avatar) - className="little-spacer-right" - name="John Doe" - size={20} - /> - <strong> - user.x_deleted.John Doe - </strong> - <span - className="little-spacer-left" - > - hotspots.tabs.review_history.created - </span> - <span - className="little-spacer-left little-spacer-right" - > - - - </span> - <DateTimeFormatter - date="2019-09-13T17:55:42+0200" - /> - </div> - </div> - <hr /> - <div - className="padded" - > - <div - className="display-flex-center" - > - <DateTimeFormatter - date="2019-09-13T17:55:42+0200" - /> - </div> - </div> - <hr /> - <div - className="padded" - > - <div - className="display-flex-center" - > - <Connect(Avatar) - className="little-spacer-right" - name="John Doe" - size={20} - /> - <strong> - John Doe - </strong> - <span - className="little-spacer-left little-spacer-right" - > - - - </span> - <DateTimeFormatter - date="2019-09-13T17:55:42+0200" - /> - </div> - <div - className="spacer-top" - > - <IssueChangelogDiff - diff={ - Object { - "key": "test", - "newValue": "new", - "oldValue": "old", - } - } - key="0" - /> - <IssueChangelogDiff - diff={ - Object { - "key": "test-1", - "newValue": "new-1", - "oldValue": "old-1", - } - } - key="1" - /> - </div> - </div> - <hr /> - <div - className="padded" - > - <div - className="display-flex-center" - > - <Connect(Avatar) - className="little-spacer-right" - name="John Doe" - size={20} - /> - <strong> - John Doe - </strong> - <span - className="little-spacer-left" - > - hotspots.tabs.review_history.comment.added - </span> - <span - className="little-spacer-left little-spacer-right" - > - - - </span> - <DateTimeFormatter - date="2019-09-13T17:55:42+0200" - /> - </div> - <div - className="spacer-top markdown" - dangerouslySetInnerHTML={ - Object { - "__html": "<strong>bold text</strong>", - } - } - /> - </div> - <hr /> - <HotspotViewerReviewHistoryTabCommentBox - hotspot={ - Object { - "assignee": "assignee", - "assigneeUser": Object { - "active": true, - "local": true, - "login": "assignee", - "name": "John Doe", - }, - "author": "author", - "authorUser": Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - "canChangeStatus": true, - "changelog": Array [], - "comment": Array [], - "component": Object { - "breadcrumbs": Array [], - "key": "my-project", - "name": "MyProject", - "organization": "foo", - "qualifier": "FIL", - "qualityGate": Object { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": Array [ - Object { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": Array [], - }, - "creationDate": "2013-05-13T17:55:41+0200", - "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", - "line": 142, - "message": "'3' is a magic number.", - "project": Object { - "breadcrumbs": Array [], - "key": "my-project", - "name": "MyProject", - "organization": "foo", - "qualifier": "TRK", - "qualityGate": Object { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": Array [ - Object { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": Array [], - }, - "resolution": "FIXED", - "rule": Object { - "fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", - "key": "squid:S2077", - "name": "That rule", - "riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", - "securityCategory": "sql-injection", - "vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", - "vulnerabilityProbability": "HIGH", - }, - "status": "REVIEWED", - "textRange": Object { - "endLine": 142, - "endOffset": 83, - "startLine": 142, - "startOffset": 26, - }, - "updateDate": "2013-05-13T17:55:42+0200", - "users": Array [ - Object { - "active": true, - "local": true, - "login": "assignee", - "name": "John Doe", - }, - Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - ], - } - } - onUpdateHotspot={[MockFunction]} - /> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerReviewHistoryTabCommentBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerReviewHistoryTabCommentBox-test.tsx.snap deleted file mode 100644 index 951a828e1b2..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerReviewHistoryTabCommentBox-test.tsx.snap +++ /dev/null @@ -1,78 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should properly submit a comment: without comment box 1`] = ` -<div - className="big-spacer-top" -> - <Button - id="hotspot-comment-box-display" - onClick={[Function]} - > - hotspots.tabs.review_history.comment.add - </Button> -</div> -`; - -exports[`should render correctly 1`] = ` -<div - className="big-spacer-top" -> - <Button - id="hotspot-comment-box-display" - onClick={[Function]} - > - hotspots.tabs.review_history.comment.add - </Button> -</div> -`; - -exports[`should render correctly: with comment box 1`] = ` -<div - className="big-spacer-top" -> - <div - className="little-spacer-bottom" - > - hotspots.tabs.review_history.comment.field - </div> - <textarea - autoFocus={true} - className="form-field fixed-width width-100 spacer-bottom" - onChange={[Function]} - rows={2} - /> - <div - className="display-flex-space-between display-flex-center" - > - <MarkdownTips /> - <div> - <Button - id="hotspot-comment-box-submit" - onClick={[Function]} - > - hotspots.tabs.review_history.comment.submit - </Button> - <ResetButtonLink - className="spacer-left" - id="hotspot-comment-box-cancel" - onClick={[Function]} - > - cancel - </ResetButtonLink> - </div> - </div> -</div> -`; - -exports[`should render correctly: without comment box 1`] = ` -<div - className="big-spacer-top" -> - <Button - id="hotspot-comment-box-display" - onClick={[Function]} - > - hotspots.tabs.review_history.comment.add - </Button> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerTabs-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerTabs-test.tsx.snap index 76d3667a4ff..e6aa9f51182 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerTabs-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerTabs-test.tsx.snap @@ -22,130 +22,6 @@ exports[`should render correctly: fix 1`] = ` "key": "fix", "label": "hotspots.tabs.fix_recommendations", }, - Object { - "content": <HotspotViewerReviewHistoryTab - history={ - Array [ - Object { - "date": "2013-05-13T17:55:41+0200", - "type": 0, - "user": Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - }, - ] - } - hotspot={ - Object { - "assignee": "assignee", - "assigneeUser": Object { - "active": true, - "local": true, - "login": "assignee", - "name": "John Doe", - }, - "author": "author", - "authorUser": Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - "canChangeStatus": true, - "changelog": Array [], - "comment": Array [], - "component": Object { - "breadcrumbs": Array [], - "key": "my-project", - "name": "MyProject", - "organization": "foo", - "qualifier": "FIL", - "qualityGate": Object { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": Array [ - Object { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": Array [], - }, - "creationDate": "2013-05-13T17:55:41+0200", - "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", - "line": 142, - "message": "'3' is a magic number.", - "project": Object { - "breadcrumbs": Array [], - "key": "my-project", - "name": "MyProject", - "organization": "foo", - "qualifier": "TRK", - "qualityGate": Object { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": Array [ - Object { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": Array [], - }, - "resolution": "FIXED", - "rule": Object { - "fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", - "key": "squid:S2077", - "name": "That rule", - "riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", - "securityCategory": "sql-injection", - "vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", - "vulnerabilityProbability": "HIGH", - }, - "status": "REVIEWED", - "textRange": Object { - "endLine": 142, - "endOffset": 83, - "startLine": 142, - "startOffset": 26, - }, - "updateDate": "2013-05-13T17:55:42+0200", - "users": Array [ - Object { - "active": true, - "local": true, - "login": "assignee", - "name": "John Doe", - }, - Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - ], - } - } - onUpdateHotspot={[MockFunction]} - />, - "key": "review", - "label": <React.Fragment> - <span> - hotspots.tabs.review_history - </span> - </React.Fragment>, - }, ] } /> @@ -164,278 +40,6 @@ exports[`should render correctly: fix 1`] = ` </Fragment> `; -exports[`should render correctly: review 1`] = ` -<Fragment> - <BoxedTabs - onSelect={[Function]} - selected="review" - tabs={ - Array [ - Object { - "content": "<p>This a <strong>strong</strong> message about risk !</p>", - "key": "risk", - "label": "hotspots.tabs.risk_description", - }, - Object { - "content": "<p>This a <strong>strong</strong> message about vulnerability !</p>", - "key": "vulnerability", - "label": "hotspots.tabs.vulnerability_description", - }, - Object { - "content": "<p>This a <strong>strong</strong> message about fixing !</p>", - "key": "fix", - "label": "hotspots.tabs.fix_recommendations", - }, - Object { - "content": <HotspotViewerReviewHistoryTab - history={ - Array [ - Object { - "date": "2013-05-13T17:55:41+0200", - "type": 0, - "user": Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - }, - ] - } - hotspot={ - Object { - "assignee": "assignee", - "assigneeUser": Object { - "active": true, - "local": true, - "login": "assignee", - "name": "John Doe", - }, - "author": "author", - "authorUser": Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - "canChangeStatus": true, - "changelog": Array [], - "comment": Array [], - "component": Object { - "breadcrumbs": Array [], - "key": "my-project", - "name": "MyProject", - "organization": "foo", - "qualifier": "FIL", - "qualityGate": Object { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": Array [ - Object { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": Array [], - }, - "creationDate": "2013-05-13T17:55:41+0200", - "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", - "line": 142, - "message": "'3' is a magic number.", - "project": Object { - "breadcrumbs": Array [], - "key": "my-project", - "name": "MyProject", - "organization": "foo", - "qualifier": "TRK", - "qualityGate": Object { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": Array [ - Object { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": Array [], - }, - "resolution": "FIXED", - "rule": Object { - "fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", - "key": "squid:S2077", - "name": "That rule", - "riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", - "securityCategory": "sql-injection", - "vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", - "vulnerabilityProbability": "HIGH", - }, - "status": "REVIEWED", - "textRange": Object { - "endLine": 142, - "endOffset": 83, - "startLine": 142, - "startOffset": 26, - }, - "updateDate": "2013-05-13T17:55:42+0200", - "users": Array [ - Object { - "active": true, - "local": true, - "login": "assignee", - "name": "John Doe", - }, - Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - ], - } - } - onUpdateHotspot={[MockFunction]} - />, - "key": "review", - "label": <React.Fragment> - <span> - hotspots.tabs.review_history - </span> - </React.Fragment>, - }, - ] - } - /> - <div - className="bordered huge-spacer-bottom" - > - <HotspotViewerReviewHistoryTab - history={ - Array [ - Object { - "date": "2013-05-13T17:55:41+0200", - "type": 0, - "user": Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - }, - ] - } - hotspot={ - Object { - "assignee": "assignee", - "assigneeUser": Object { - "active": true, - "local": true, - "login": "assignee", - "name": "John Doe", - }, - "author": "author", - "authorUser": Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - "canChangeStatus": true, - "changelog": Array [], - "comment": Array [], - "component": Object { - "breadcrumbs": Array [], - "key": "my-project", - "name": "MyProject", - "organization": "foo", - "qualifier": "FIL", - "qualityGate": Object { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": Array [ - Object { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": Array [], - }, - "creationDate": "2013-05-13T17:55:41+0200", - "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", - "line": 142, - "message": "'3' is a magic number.", - "project": Object { - "breadcrumbs": Array [], - "key": "my-project", - "name": "MyProject", - "organization": "foo", - "qualifier": "TRK", - "qualityGate": Object { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": Array [ - Object { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": Array [], - }, - "resolution": "FIXED", - "rule": Object { - "fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", - "key": "squid:S2077", - "name": "That rule", - "riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", - "securityCategory": "sql-injection", - "vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", - "vulnerabilityProbability": "HIGH", - }, - "status": "REVIEWED", - "textRange": Object { - "endLine": 142, - "endOffset": 83, - "startLine": 142, - "startOffset": 26, - }, - "updateDate": "2013-05-13T17:55:42+0200", - "users": Array [ - Object { - "active": true, - "local": true, - "login": "assignee", - "name": "John Doe", - }, - Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - ], - } - } - onUpdateHotspot={[MockFunction]} - /> - </div> -</Fragment> -`; - exports[`should render correctly: risk 1`] = ` <Fragment> <BoxedTabs @@ -458,130 +62,6 @@ exports[`should render correctly: risk 1`] = ` "key": "fix", "label": "hotspots.tabs.fix_recommendations", }, - Object { - "content": <HotspotViewerReviewHistoryTab - history={ - Array [ - Object { - "date": "2013-05-13T17:55:41+0200", - "type": 0, - "user": Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - }, - ] - } - hotspot={ - Object { - "assignee": "assignee", - "assigneeUser": Object { - "active": true, - "local": true, - "login": "assignee", - "name": "John Doe", - }, - "author": "author", - "authorUser": Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - "canChangeStatus": true, - "changelog": Array [], - "comment": Array [], - "component": Object { - "breadcrumbs": Array [], - "key": "my-project", - "name": "MyProject", - "organization": "foo", - "qualifier": "FIL", - "qualityGate": Object { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": Array [ - Object { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": Array [], - }, - "creationDate": "2013-05-13T17:55:41+0200", - "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", - "line": 142, - "message": "'3' is a magic number.", - "project": Object { - "breadcrumbs": Array [], - "key": "my-project", - "name": "MyProject", - "organization": "foo", - "qualifier": "TRK", - "qualityGate": Object { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": Array [ - Object { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": Array [], - }, - "resolution": "FIXED", - "rule": Object { - "fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", - "key": "squid:S2077", - "name": "That rule", - "riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", - "securityCategory": "sql-injection", - "vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", - "vulnerabilityProbability": "HIGH", - }, - "status": "REVIEWED", - "textRange": Object { - "endLine": 142, - "endOffset": 83, - "startLine": 142, - "startOffset": 26, - }, - "updateDate": "2013-05-13T17:55:42+0200", - "users": Array [ - Object { - "active": true, - "local": true, - "login": "assignee", - "name": "John Doe", - }, - Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - ], - } - } - onUpdateHotspot={[MockFunction]} - />, - "key": "review", - "label": <React.Fragment> - <span> - hotspots.tabs.review_history - </span> - </React.Fragment>, - }, ] } /> @@ -622,130 +102,6 @@ exports[`should render correctly: vulnerability 1`] = ` "key": "fix", "label": "hotspots.tabs.fix_recommendations", }, - Object { - "content": <HotspotViewerReviewHistoryTab - history={ - Array [ - Object { - "date": "2013-05-13T17:55:41+0200", - "type": 0, - "user": Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - }, - ] - } - hotspot={ - Object { - "assignee": "assignee", - "assigneeUser": Object { - "active": true, - "local": true, - "login": "assignee", - "name": "John Doe", - }, - "author": "author", - "authorUser": Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - "canChangeStatus": true, - "changelog": Array [], - "comment": Array [], - "component": Object { - "breadcrumbs": Array [], - "key": "my-project", - "name": "MyProject", - "organization": "foo", - "qualifier": "FIL", - "qualityGate": Object { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": Array [ - Object { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": Array [], - }, - "creationDate": "2013-05-13T17:55:41+0200", - "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", - "line": 142, - "message": "'3' is a magic number.", - "project": Object { - "breadcrumbs": Array [], - "key": "my-project", - "name": "MyProject", - "organization": "foo", - "qualifier": "TRK", - "qualityGate": Object { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": Array [ - Object { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": Array [], - }, - "resolution": "FIXED", - "rule": Object { - "fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", - "key": "squid:S2077", - "name": "That rule", - "riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", - "securityCategory": "sql-injection", - "vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", - "vulnerabilityProbability": "HIGH", - }, - "status": "REVIEWED", - "textRange": Object { - "endLine": 142, - "endOffset": 83, - "startLine": 142, - "startOffset": 26, - }, - "updateDate": "2013-05-13T17:55:42+0200", - "users": Array [ - Object { - "active": true, - "local": true, - "login": "assignee", - "name": "John Doe", - }, - Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - ], - } - } - onUpdateHotspot={[MockFunction]} - />, - "key": "review", - "label": <React.Fragment> - <span> - hotspots.tabs.review_history - </span> - </React.Fragment>, - }, ] } /> @@ -786,161 +142,6 @@ exports[`should render correctly: with comments or changelog element 1`] = ` "key": "fix", "label": "hotspots.tabs.fix_recommendations", }, - Object { - "content": <HotspotViewerReviewHistoryTab - history={ - Array [ - Object { - "date": "2013-05-13T17:55:41+0200", - "type": 0, - "user": Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - }, - Object { - "date": "2019-01-01", - "html": "<strong>test</strong>", - "type": 2, - "user": Object { - "active": true, - "local": true, - "login": "john.doe", - "name": "John Doe", - }, - }, - ] - } - hotspot={ - Object { - "assignee": "assignee", - "assigneeUser": Object { - "active": true, - "local": true, - "login": "assignee", - "name": "John Doe", - }, - "author": "author", - "authorUser": Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - "canChangeStatus": true, - "changelog": Array [], - "comment": Array [ - Object { - "createdAt": "2019-01-01", - "htmlText": "<strong>test</strong>", - "key": "comment-key", - "login": "me", - "markdown": "*test*", - "updatable": false, - "user": Object { - "active": true, - "local": true, - "login": "john.doe", - "name": "John Doe", - }, - }, - ], - "component": Object { - "breadcrumbs": Array [], - "key": "my-project", - "name": "MyProject", - "organization": "foo", - "qualifier": "FIL", - "qualityGate": Object { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": Array [ - Object { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": Array [], - }, - "creationDate": "2013-05-13T17:55:41+0200", - "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", - "line": 142, - "message": "'3' is a magic number.", - "project": Object { - "breadcrumbs": Array [], - "key": "my-project", - "name": "MyProject", - "organization": "foo", - "qualifier": "TRK", - "qualityGate": Object { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": Array [ - Object { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": Array [], - }, - "resolution": "FIXED", - "rule": Object { - "fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", - "key": "squid:S2077", - "name": "That rule", - "riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", - "securityCategory": "sql-injection", - "vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", - "vulnerabilityProbability": "HIGH", - }, - "status": "REVIEWED", - "textRange": Object { - "endLine": 142, - "endOffset": 83, - "startLine": 142, - "startOffset": 26, - }, - "updateDate": "2013-05-13T17:55:42+0200", - "users": Array [ - Object { - "active": true, - "local": true, - "login": "assignee", - "name": "John Doe", - }, - Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - ], - } - } - onUpdateHotspot={[MockFunction]} - />, - "key": "review", - "label": <React.Fragment> - <span> - hotspots.tabs.review_history - </span> - <span - className="counter-badge spacer-left" - > - 1 - </span> - </React.Fragment>, - }, ] } /> diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/styles.css b/server/sonar-web/src/main/js/apps/security-hotspots/styles.css index f95c95b6331..69d689e6df0 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/styles.css +++ b/server/sonar-web/src/main/js/apps/security-hotspots/styles.css @@ -48,6 +48,17 @@ #security_hotspots .main { flex: 1 1 auto; - overflow-y: auto; background-color: white; + /* Force flex to take parent width. */ + overflow-x: auto; +} + +#security_hotspots .main .hotspot-content { + overflow-y: auto; + height: 100%; + box-sizing: border-box; +} + +.invisible { + visibility: hidden; } diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/utils.ts b/server/sonar-web/src/main/js/apps/security-hotspots/utils.ts index c0fe1efc968..06c3898631b 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/utils.ts +++ b/server/sonar-web/src/main/js/apps/security-hotspots/utils.ts @@ -81,14 +81,8 @@ export function constructSourceViewerFile( }; } -export function getHotspotReviewHistory( - hotspot: Hotspot -): { - history: ReviewHistoryElement[]; - functionalCount: number; -} { +export function getHotspotReviewHistory(hotspot: Hotspot): ReviewHistoryElement[] { const history: ReviewHistoryElement[] = []; - let functionalCount = 0; if (hotspot.creationDate) { history.push({ @@ -102,7 +96,6 @@ export function getHotspotReviewHistory( } if (hotspot.changelog && hotspot.changelog.length > 0) { - functionalCount += hotspot.changelog.length; history.push( ...hotspot.changelog.map(log => ({ type: ReviewHistoryType.Diff, @@ -118,7 +111,6 @@ export function getHotspotReviewHistory( } if (hotspot.comment && hotspot.comment.length > 0) { - functionalCount += hotspot.comment.length; history.push( ...hotspot.comment.map(comment => ({ type: ReviewHistoryType.Comment, @@ -132,10 +124,7 @@ export function getHotspotReviewHistory( ); } - return { - history: sortBy(history, elt => elt.date), - functionalCount - }; + return sortBy(history, elt => elt.date); } const STATUS_AND_RESOLUTION_TO_STATUS_OPTION = { 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 880fabbf057..aa865428be5 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -667,12 +667,11 @@ hotspots.risk_exposure=Review priority hotspots.tabs.risk_description=What's the risk? hotspots.tabs.vulnerability_description=Are you at risk? hotspots.tabs.fix_recommendations=How can you fix it? -hotspots.tabs.review_history=Review history -hotspots.tabs.review_history.created=created Security Hotspot -hotspots.tabs.review_history.comment.added=added a comment -hotspots.tabs.review_history.comment.add=Add a comment -hotspots.tabs.review_history.comment.field=Comment: -hotspots.tabs.review_history.comment.submit=Comment +hotspots.review_history.created=created Security Hotspot +hotspots.review_history.comment_added=added a comment +hotspots.comment.field=Comment: +hotspots.comment.open=Add Comment +hotspots.comment.submit=Comment hotspots.assignee.select_user=Select a user... hotspots.status.cannot_change_status=Changing a hotspot's status requires permission. @@ -696,6 +695,7 @@ hotspot.filters.period.since_leak_period=New code hotspot.filters.period.overall=Overall code hotspot.filters.status.safe=Reviewed as safe hotspot.filters.show_all=Show all hotspots +hotspot.section.activity=Activity: hotspots.reviewed.tooltip=Percentage of Security Hotspots reviewed (fixed or safe) among all non-closed Security Hotspots. hotspots.review_hotspot=Review Hotspot |