Browse Source

SONAR-12720 Put comment on bottom of hotspot review page and add a link from top.

tags/8.2.0.32929
Mathieu Suen 4 years ago
parent
commit
9bc1ac6000
24 changed files with 1695 additions and 1587 deletions
  1. 5
    6
      server/sonar-web/src/main/js/apps/security-hotspots/__tests__/utils-test.ts
  2. 29
    36
      server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistory.tsx
  3. 131
    0
      server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistoryAndComments.tsx
  4. 40
    3
      server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx
  5. 48
    8
      server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx
  6. 0
    89
      server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerReviewHistoryTabCommentBox.tsx
  7. 6
    35
      server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx
  8. 69
    0
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistory-test.tsx
  9. 111
    0
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistoryAndComments-test.tsx
  10. 55
    0
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewer-test.tsx
  11. 10
    2
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerRenderer-test.tsx
  12. 0
    73
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerReviewHistoryTab-test.tsx
  13. 0
    72
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerReviewHistoryTabCommentBox-test.tsx
  14. 3
    22
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerTabs-test.tsx
  15. 151
    0
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistory-test.tsx.snap
  16. 277
    0
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistoryAndComments-test.tsx.snap
  17. 28
    2
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewer-test.tsx.snap
  18. 712
    72
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap
  19. 0
    270
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerReviewHistoryTab-test.tsx.snap
  20. 0
    78
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerReviewHistoryTabCommentBox-test.tsx.snap
  21. 0
    799
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerTabs-test.tsx.snap
  22. 12
    1
      server/sonar-web/src/main/js/apps/security-hotspots/styles.css
  23. 2
    13
      server/sonar-web/src/main/js/apps/security-hotspots/utils.ts
  24. 6
    6
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 5
- 6
server/sonar-web/src/main/js/apps/security-hotspots/__tests__/utils-test.ts View File

@@ -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,

server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerReviewHistoryTab.tsx → server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistory.tsx View File

@@ -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>
);
})}
</>
);
}

+ 131
- 0
server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistoryAndComments.tsx View File

@@ -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>
</>
);
}
}

+ 40
- 3
server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx View File

@@ -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}
/>
);

+ 48
- 8
server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx View File

@@ -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);

+ 0
- 89
server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerReviewHistoryTabCommentBox.tsx View File

@@ -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>
);
}

+ 6
- 35
server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx View File

@@ -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>
</>
);

+ 69
- 0
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistory-test.tsx View File

@@ -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} />);
}

+ 111
- 0
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistoryAndComments-test.tsx View File

@@ -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}
/>
);
}

+ 55
- 0
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewer-test.tsx View File

@@ -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

+ 10
- 2
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerRenderer-test.tsx View File

@@ -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}
/>

+ 0
- 73
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerReviewHistoryTab-test.tsx View File

@@ -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}
/>
);
}

+ 0
- 72
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerReviewHistoryTabCommentBox-test.tsx View File

@@ -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}
/>
);
}

+ 3
- 22
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerTabs-test.tsx View File

@@ -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} />);
}

+ 151
- 0
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistory-test.tsx.snap View File

@@ -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>
`;

+ 277
- 0
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistoryAndComments-test.tsx.snap View File

@@ -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>
`;

+ 28
- 2
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewer-test.tsx.snap View File

@@ -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 {

+ 712
- 72
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap View File

@@ -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,38 +1755,166 @@ exports[`should render correctly: assignee without name 1`] = `
],
}
}
onUpdateHotspot={[MockFunction]}
/>
</div>
</DeferredSpinner>
`;

exports[`should render correctly: deleted assignee 1`] = `
<DeferredSpinner
loading={false}
timeout={100}
>
<div
className="big-padded"
>
<div
className="huge-spacer-bottom display-flex-space-between"
>
<strong
className="big big-spacer-right"
>
<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>
`;

exports[`should render correctly: deleted assignee 1`] = `
<DeferredSpinner
loading={false}
timeout={100}
>
<div
className="big-padded hotspot-content"
>
<div
className="huge-spacer-bottom display-flex-space-between"
>
<strong
className="big big-spacer-right"
>
'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>

+ 0
- 270
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerReviewHistoryTab-test.tsx.snap View File

@@ -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>
`;

+ 0
- 78
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerReviewHistoryTabCommentBox-test.tsx.snap View File

@@ -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>
`;

+ 0
- 799
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerTabs-test.tsx.snap View File

@@ -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>,
},
]
}
/>

+ 12
- 1
server/sonar-web/src/main/js/apps/security-hotspots/styles.css View File

@@ -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;
}

+ 2
- 13
server/sonar-web/src/main/js/apps/security-hotspots/utils.ts View File

@@ -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 = {

+ 6
- 6
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -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

Loading…
Cancel
Save