aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/__tests__/utils-test.ts8
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistory.tsx211
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistoryAndComments.tsx110
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx29
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistory-test.tsx175
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistoryAndComments-test.tsx40
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewer-test.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerRenderer-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistory-test.tsx.snap784
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistoryAndComments-test.tsx.snap371
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewer-test.tsx.snap8
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap18
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/utils.ts2
-rw-r--r--server/sonar-web/src/main/js/helpers/mocks/issues.ts17
-rw-r--r--server/sonar-web/src/main/js/helpers/mocks/security-hotspots.ts14
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties3
17 files changed, 1136 insertions, 682 deletions
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/utils-test.ts
index f62e89a980e..295b6bc6714 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/utils-test.ts
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/utils-test.ts
@@ -191,14 +191,14 @@ describe('getHotspotReviewHistory', () => {
const reviewHistory = getHotspotReviewHistory(hotspot);
expect(reviewHistory.length).toBe(4);
- expect(reviewHistory[0]).toEqual(
+ expect(reviewHistory[3]).toEqual(
expect.objectContaining({
type: ReviewHistoryType.Creation,
date: hotspot.creationDate,
user: hotspot.authorUser
})
);
- expect(reviewHistory[1]).toEqual(
+ expect(reviewHistory[2]).toEqual(
expect.objectContaining({
type: ReviewHistoryType.Comment,
date: commentElement.createdAt,
@@ -206,7 +206,7 @@ describe('getHotspotReviewHistory', () => {
html: commentElement.htmlText
})
);
- expect(reviewHistory[2]).toEqual(
+ expect(reviewHistory[1]).toEqual(
expect.objectContaining({
type: ReviewHistoryType.Comment,
date: commentElement1.createdAt,
@@ -214,7 +214,7 @@ describe('getHotspotReviewHistory', () => {
html: commentElement1.htmlText
})
);
- expect(reviewHistory[3]).toEqual(
+ expect(reviewHistory[0]).toEqual(
expect.objectContaining({
type: ReviewHistoryType.Diff,
date: changelogElement.creationDate,
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistory.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistory.tsx
index fd320921e0f..e3cf11a8a23 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistory.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistory.tsx
@@ -19,7 +19,7 @@
*/
import * as classNames from 'classnames';
import * as React from 'react';
-import { Button, DeleteButton, EditButton } from '../../../components/controls/buttons';
+import { Button, ButtonLink, DeleteButton, EditButton } from '../../../components/controls/buttons';
import Dropdown, { DropdownOverlay } from '../../../components/controls/Dropdown';
import Toggler from '../../../components/controls/Toggler';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
@@ -36,112 +36,131 @@ export interface HotspotReviewHistoryProps {
hotspot: Hotspot;
onDeleteComment: (key: string) => void;
onEditComment: (key: string, comment: string) => void;
+ onShowFullHistory: () => void;
+ showFullHistory: boolean;
}
+export const MAX_RECENT_ACTIVITY = 5;
+
export default function HotspotReviewHistory(props: HotspotReviewHistoryProps) {
- const { hotspot } = props;
- const reviewHistory = getHotspotReviewHistory(hotspot);
+ const { hotspot, showFullHistory } = props;
+ const fullReviewHistory = getHotspotReviewHistory(hotspot);
const [editedCommentKey, setEditedCommentKey] = React.useState('');
+ const reviewHistory = showFullHistory
+ ? fullReviewHistory
+ : fullReviewHistory.slice(0, MAX_RECENT_ACTIVITY);
+
return (
<>
- {reviewHistory.map((historyElt, historyIndex) => {
- const { user, type, diffs, date, html, key, updatable, markdown } = historyElt;
- return (
- <div
- className={classNames('padded', { 'bordered-top': historyIndex > 0 })}
- key={historyIndex}>
- <div className="display-flex-center">
- {user.name && (
- <>
- <Avatar
- className="little-spacer-right"
- hash={user.avatar}
- name={user.name}
- size={20}
- />
- <strong>
- {user.active ? user.name : translateWithParameters('user.x_deleted', user.name)}
- </strong>
- {type === ReviewHistoryType.Creation && (
- <span className="little-spacer-left">
- {translate('hotspots.review_history.created')}
- </span>
- )}
- {type === ReviewHistoryType.Comment && (
- <span className="little-spacer-left">
- {translate('hotspots.review_history.comment_added')}
- </span>
- )}
- <span className="little-spacer-left little-spacer-right">-</span>
- </>
- )}
- <DateTimeFormatter date={date} />
- </div>
-
- {type === ReviewHistoryType.Diff && diffs && (
- <div className="spacer-top">
- {diffs.map((diff, diffIndex) => (
- <IssueChangelogDiff diff={diff} key={diffIndex} />
- ))}
+ <ul>
+ {reviewHistory.map((historyElt, historyIndex) => {
+ const { user, type, diffs, date, html, key, updatable, markdown } = historyElt;
+ return (
+ <li
+ className={classNames('padded-top padded-bottom', {
+ 'bordered-top': historyIndex > 0
+ })}
+ key={historyIndex}>
+ <div className="display-flex-center">
+ {user.name && (
+ <>
+ <Avatar
+ className="little-spacer-right"
+ hash={user.avatar}
+ name={user.name}
+ size={20}
+ />
+ <strong>
+ {user.active
+ ? user.name
+ : translateWithParameters('user.x_deleted', user.name)}
+ </strong>
+ {type === ReviewHistoryType.Creation && (
+ <span className="little-spacer-left">
+ {translate('hotspots.review_history.created')}
+ </span>
+ )}
+ {type === ReviewHistoryType.Comment && (
+ <span className="little-spacer-left">
+ {translate('hotspots.review_history.comment_added')}
+ </span>
+ )}
+ <span className="little-spacer-left little-spacer-right">-</span>
+ </>
+ )}
+ <DateTimeFormatter date={date} />
</div>
- )}
- {type === ReviewHistoryType.Comment && key && html && markdown && (
- <div className="spacer-top display-flex-space-between">
- <div
- className="markdown"
- // eslint-disable-next-line react/no-danger
- dangerouslySetInnerHTML={{ __html: sanitizeString(html) }}
- />
- {updatable && (
- <div>
- <div className="dropdown">
- <Toggler
- onRequestClose={() => {
- setEditedCommentKey('');
- }}
- open={key === editedCommentKey}
+ {type === ReviewHistoryType.Diff && diffs && (
+ <div className="spacer-top">
+ {diffs.map((diff, diffIndex) => (
+ <IssueChangelogDiff diff={diff} key={diffIndex} />
+ ))}
+ </div>
+ )}
+
+ {type === ReviewHistoryType.Comment && key && html && markdown && (
+ <div className="spacer-top display-flex-space-between">
+ <div
+ className="markdown"
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: sanitizeString(html) }}
+ />
+ {updatable && (
+ <div>
+ <div className="dropdown">
+ <Toggler
+ onRequestClose={() => {
+ setEditedCommentKey('');
+ }}
+ open={key === editedCommentKey}
+ overlay={
+ <DropdownOverlay placement={PopupPlacement.BottomRight}>
+ <HotspotCommentPopup
+ markdownComment={markdown}
+ onCancelEdit={() => setEditedCommentKey('')}
+ onCommentEditSubmit={comment => {
+ setEditedCommentKey('');
+ props.onEditComment(key, comment);
+ }}
+ />
+ </DropdownOverlay>
+ }>
+ <EditButton
+ className="button-small"
+ onClick={() => setEditedCommentKey(key)}
+ />
+ </Toggler>
+ </div>
+ <Dropdown
+ onOpen={() => setEditedCommentKey('')}
overlay={
- <DropdownOverlay placement={PopupPlacement.BottomRight}>
- <HotspotCommentPopup
- markdownComment={markdown}
- onCancelEdit={() => setEditedCommentKey('')}
- onCommentEditSubmit={comment => {
- setEditedCommentKey('');
- props.onEditComment(key, comment);
- }}
- />
- </DropdownOverlay>
- }>
- <EditButton
- className="button-small"
- onClick={() => setEditedCommentKey(key)}
- />
- </Toggler>
+ <div className="padded abs-width-150">
+ <p>{translate('issue.comment.delete_confirm_message')}</p>
+ <Button
+ className="button-red big-spacer-top pull-right"
+ onClick={() => props.onDeleteComment(key)}>
+ {translate('delete')}
+ </Button>
+ </div>
+ }
+ overlayPlacement={PopupPlacement.BottomRight}>
+ <DeleteButton className="button-small" />
+ </Dropdown>
</div>
- <Dropdown
- onOpen={() => setEditedCommentKey('')}
- overlay={
- <div className="padded abs-width-150">
- <p>{translate('issue.comment.delete_confirm_message')}</p>
- <Button
- className="button-red big-spacer-top pull-right"
- onClick={() => props.onDeleteComment(key)}>
- {translate('delete')}
- </Button>
- </div>
- }
- overlayPlacement={PopupPlacement.BottomRight}>
- <DeleteButton className="button-small" />
- </Dropdown>
- </div>
- )}
- </div>
- )}
- </div>
- );
- })}
+ )}
+ </div>
+ )}
+ </li>
+ );
+ })}
+ </ul>
+ {!showFullHistory && fullReviewHistory.length > MAX_RECENT_ACTIVITY && (
+ <ButtonLink className="spacer-top" onClick={props.onShowFullHistory}>
+ {translate('show_all')}
+ </ButtonLink>
+ )}
</>
);
}
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistoryAndComments.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistoryAndComments.tsx
index 42d9a2fbc92..b358850b3ca 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistoryAndComments.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistoryAndComments.tsx
@@ -17,7 +17,6 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import * as classNames from 'classnames';
import * as React from 'react';
import {
commentSecurityHotspot,
@@ -25,7 +24,7 @@ import {
editSecurityHotspotComment
} from '../../../api/security-hotspots';
import FormattingTips from '../../../components/common/FormattingTips';
-import { Button, ResetButtonLink } from '../../../components/controls/buttons';
+import { Button } from '../../../components/controls/buttons';
import { translate } from '../../../helpers/l10n';
import { isLoggedIn } from '../../../helpers/users';
import { Hotspot } from '../../../types/security-hotspots';
@@ -35,28 +34,28 @@ interface Props {
currentUser: T.CurrentUser;
hotspot: Hotspot;
commentTextRef: React.RefObject<HTMLTextAreaElement>;
- commentVisible: boolean;
onCommentUpdate: () => void;
- onOpenComment: () => void;
- onCloseComment: () => void;
}
interface State {
comment: string;
+ showFullHistory: boolean;
}
export default class HotspotReviewHistoryAndComments extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
- comment: ''
+ comment: '',
+ showFullHistory: false
};
}
componentDidUpdate(prevProps: Props) {
- if (prevProps.hotspot !== this.props.hotspot) {
+ if (prevProps.hotspot.key !== this.props.hotspot.key) {
this.setState({
- comment: ''
+ comment: '',
+ showFullHistory: false
});
}
}
@@ -65,15 +64,9 @@ export default class HotspotReviewHistoryAndComments extends React.PureComponent
this.setState({ comment: event.target.value });
};
- handleCloseComment = () => {
- this.setState({ comment: '' });
- this.props.onCloseComment();
- };
-
handleSubmitComment = () => {
return commentSecurityHotspot(this.props.hotspot.key, this.state.comment).then(() => {
this.setState({ comment: '' });
- this.props.onCloseComment();
this.props.onCommentUpdate();
});
};
@@ -90,62 +83,49 @@ export default class HotspotReviewHistoryAndComments extends React.PureComponent
});
};
+ handleShowFullHistory = () => {
+ this.setState({ showFullHistory: true });
+ };
+
render() {
- const { currentUser, hotspot, commentTextRef, commentVisible } = this.props;
- const { comment } = this.state;
+ const { currentUser, hotspot, commentTextRef } = this.props;
+ const { comment, showFullHistory } = this.state;
return (
- <>
- <h1>{translate('hotspot.section.activity')}</h1>
- <div className="padded it__hs-review-history">
- <HotspotReviewHistory
- hotspot={hotspot}
- onDeleteComment={this.handleDeleteComment}
- onEditComment={this.handleEditComment}
- />
-
- {isLoggedIn(currentUser) && (
- <>
- <hr />
- <div className="big-spacer-top">
+ <div className="padded it__hs-review-history">
+ {isLoggedIn(currentUser) && (
+ <div className="big-spacer-top">
+ <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 ">
+ <FormattingTips className="huge-spacer-bottom" />
+ <div>
<Button
- className={classNames('it__hs-add-comment', { invisible: commentVisible })}
- id="hotspot-comment-box-display"
- onClick={this.props.onOpenComment}>
- {translate('hotspots.comment.open')}
+ className="huge-spacer-bottom"
+ id="hotspot-comment-box-submit"
+ onClick={this.handleSubmitComment}>
+ {translate('hotspots.comment.submit')}
</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 ">
- <FormattingTips 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>
- </>
+ </div>
+ </div>
+ )}
+
+ <h2 className="spacer-top big-spacer-bottom">{translate('hotspot.section.activity')}</h2>
+
+ <HotspotReviewHistory
+ hotspot={hotspot}
+ onDeleteComment={this.handleDeleteComment}
+ onEditComment={this.handleEditComment}
+ onShowFullHistory={this.handleShowFullHistory}
+ showFullHistory={showFullHistory}
+ />
+ </div>
);
}
}
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx
index 1b8fa64017b..300d2e53178 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx
@@ -43,7 +43,6 @@ interface State {
hotspot?: Hotspot;
lastStatusChangedTo?: HotspotStatusOption;
loading: boolean;
- commentVisible: boolean;
showStatusUpdateSuccessModal: boolean;
}
@@ -55,7 +54,7 @@ export default class HotspotViewer extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.commentTextRef = React.createRef<HTMLTextAreaElement>();
- this.state = { loading: false, commentVisible: false, showStatusUpdateSuccessModal: false };
+ this.state = { loading: false, showStatusUpdateSuccessModal: false };
}
componentDidMount() {
@@ -63,11 +62,11 @@ export default class HotspotViewer extends React.PureComponent<Props, State> {
this.fetchHotspot();
}
- componentDidUpdate(prevProps: Props, prevState: State) {
+ componentDidUpdate(prevProps: Props) {
if (prevProps.hotspotKey !== this.props.hotspotKey) {
this.fetchHotspot();
}
- if (this.commentTextRef.current && !prevState.commentVisible && this.state.commentVisible) {
+ if (this.commentTextRef.current) {
this.commentTextRef.current.focus({ preventScroll: true });
}
}
@@ -99,23 +98,15 @@ export default class HotspotViewer extends React.PureComponent<Props, State> {
}
};
- handleOpenComment = () => {
- this.setState({ commentVisible: true });
+ handleScrollToCommentForm = () => {
if (this.commentTextRef.current) {
- // Edge case when the comment is already open and unfocus.
this.commentTextRef.current.focus({ preventScroll: true });
- }
- if (this.commentTextRef.current) {
scrollToElement(this.commentTextRef.current, {
bottomOffset: 100
});
}
};
- handleCloseComment = () => {
- this.setState({ commentVisible: false });
- };
-
handleSwitchFilterToStatusOfUpdatedHotspot = () => {
const { lastStatusChangedTo } = this.state;
if (lastStatusChangedTo) {
@@ -129,28 +120,20 @@ export default class HotspotViewer extends React.PureComponent<Props, State> {
render() {
const { branchLike, component, hotspotsReviewedMeasure, securityCategories } = this.props;
- const {
- hotspot,
- lastStatusChangedTo,
- loading,
- commentVisible,
- showStatusUpdateSuccessModal
- } = this.state;
+ const { hotspot, lastStatusChangedTo, loading, showStatusUpdateSuccessModal } = this.state;
return (
<HotspotViewerRenderer
branchLike={branchLike}
component={component}
commentTextRef={this.commentTextRef}
- commentVisible={commentVisible}
hotspot={hotspot}
hotspotsReviewedMeasure={hotspotsReviewedMeasure}
lastStatusChangedTo={lastStatusChangedTo}
loading={loading}
- onCloseComment={this.handleCloseComment}
onCloseStatusUpdateSuccessModal={this.handleCloseStatusUpdateSuccessModal}
- onOpenComment={this.handleOpenComment}
onSwitchFilterToStatusOfUpdatedHotspot={this.handleSwitchFilterToStatusOfUpdatedHotspot}
+ onShowCommentForm={this.handleScrollToCommentForm}
onUpdateHotspot={this.handleHotspotUpdate}
showStatusUpdateSuccessModal={showStatusUpdateSuccessModal}
securityCategories={securityCategories}
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx
index 19142ca7c0f..35231c6a120 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx
@@ -52,12 +52,10 @@ export interface HotspotViewerRendererProps {
hotspotsReviewedMeasure?: string;
lastStatusChangedTo?: HotspotStatusOption;
loading: boolean;
- commentVisible: boolean;
commentTextRef: React.RefObject<HTMLTextAreaElement>;
- onOpenComment: () => void;
- onCloseComment: () => void;
onCloseStatusUpdateSuccessModal: () => void;
onUpdateHotspot: (statusUpdate?: boolean, statusOption?: HotspotStatusOption) => Promise<void>;
+ onShowCommentForm: () => void;
onSwitchFilterToStatusOfUpdatedHotspot: () => void;
showStatusUpdateSuccessModal: boolean;
securityCategories: T.StandardSecurityCategories;
@@ -74,8 +72,7 @@ export function HotspotViewerRenderer(props: HotspotViewerRendererProps) {
lastStatusChangedTo,
showStatusUpdateSuccessModal,
securityCategories,
- commentTextRef,
- commentVisible
+ commentTextRef
} = props;
const permalink = getPathUrlAsString(
@@ -115,7 +112,7 @@ export function HotspotViewerRenderer(props: HotspotViewerRendererProps) {
{isLoggedIn(currentUser) && (
<>
<div className="dropdown spacer-right flex-1-0-auto">
- <Button className="it__hs-add-comment" onClick={props.onOpenComment}>
+ <Button className="it__hs-add-comment" onClick={props.onShowCommentForm}>
{translate('hotspots.comment.open')}
</Button>
</div>
@@ -175,12 +172,9 @@ export function HotspotViewerRenderer(props: HotspotViewerRendererProps) {
<HotspotViewerTabs hotspot={hotspot} />
<HotspotReviewHistoryAndComments
commentTextRef={commentTextRef}
- commentVisible={commentVisible}
currentUser={currentUser}
hotspot={hotspot}
- onCloseComment={props.onCloseComment}
onCommentUpdate={props.onUpdateHotspot}
- onOpenComment={props.onOpenComment}
/>
</div>
)}
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistory-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistory-test.tsx
index 143dfb93abf..8eda5958c98 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistory-test.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistory-test.tsx
@@ -19,57 +19,152 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { mockHotspot } from '../../../../helpers/mocks/security-hotspots';
+import { Button, EditButton } from '../../../../components/controls/buttons';
+import Dropdown, { DropdownOverlay } from '../../../../components/controls/Dropdown';
+import Toggler from '../../../../components/controls/Toggler';
+import { mockIssueChangelog } from '../../../../helpers/mocks/issues';
+import { mockHotspot, mockHotspotComment } from '../../../../helpers/mocks/security-hotspots';
import { mockUser } from '../../../../helpers/testMocks';
+import HotspotCommentPopup from '../HotspotCommentPopup';
import HotspotReviewHistory, { HotspotReviewHistoryProps } from '../HotspotReviewHistory';
it('should render correctly', () => {
- const wrapper = shallowRender();
- expect(wrapper).toMatchSnapshot();
+ expect(shallowRender()).toMatchSnapshot('default');
+ expect(shallowRender({ showFullHistory: true })).toMatchSnapshot('show full list');
+ expect(
+ shallowRender({ showFullHistory: true })
+ .find(Toggler)
+ .props().overlay
+ ).toMatchSnapshot('edit comment overlay');
+ expect(
+ shallowRender({ showFullHistory: true })
+ .find(Dropdown)
+ .props().overlay
+ ).toMatchSnapshot('delete comment overlay');
});
-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: false,
- login: 'dude-2',
- user: mockUser({ login: 'dude-2' })
- };
- const hotspot = mockHotspot({
- creationDate: '2018-09-01',
- changelog: [changelogElement],
- comment: [commentElement, commentElement1]
+it('should correctly handle comment updating', () => {
+ return new Promise<void>((resolve, reject) => {
+ const setEditedCommentKey = jest.fn();
+ jest.spyOn(React, 'useState').mockImplementationOnce(() => ['', setEditedCommentKey]);
+
+ const onEditComment = jest.fn();
+ const wrapper = shallowRender({ onEditComment, showFullHistory: true });
+
+ // Closing the Toggler sets the edited key back to an empty string.
+ wrapper
+ .find(Toggler)
+ .at(0)
+ .props()
+ .onRequestClose();
+ expect(setEditedCommentKey).toBeCalledWith('');
+
+ const editOnClick = wrapper
+ .find(EditButton)
+ .at(0)
+ .props().onClick;
+ if (!editOnClick) {
+ reject();
+ return;
+ }
+
+ // Clicking on the EditButton correctly flags the comment for editing.
+ editOnClick();
+ expect(setEditedCommentKey).toHaveBeenLastCalledWith('comment-1');
+
+ // Cancelling an edit sets the edited key back to an empty string
+ const dropdownOverlay = shallow(
+ wrapper
+ .find(Toggler)
+ .at(0)
+ .props().overlay as React.ReactElement<DropdownOverlay>
+ );
+ dropdownOverlay
+ .find(HotspotCommentPopup)
+ .props()
+ .onCancelEdit();
+ expect(setEditedCommentKey).toHaveBeenLastCalledWith('');
+
+ // Updating the comment sets the edited key back to an empty string, and calls the
+ // prop to update the comment value.
+ dropdownOverlay
+ .find(HotspotCommentPopup)
+ .props()
+ .onCommentEditSubmit('comment');
+ expect(onEditComment).toHaveBeenLastCalledWith('comment-1', 'comment');
+ expect(setEditedCommentKey).toHaveBeenLastCalledWith('');
+ expect(setEditedCommentKey).toHaveBeenCalledTimes(4);
+
+ resolve();
});
+});
+
+it('should correctly handle comment deleting', () => {
+ return new Promise<void>((resolve, reject) => {
+ const setEditedCommentKey = jest.fn();
+ jest.spyOn(React, 'useState').mockImplementationOnce(() => ['', setEditedCommentKey]);
+
+ const onDeleteComment = jest.fn();
+ const wrapper = shallowRender({ onDeleteComment, showFullHistory: true });
+
+ // Opening the deletion Dropdown sets the edited key back to an empty string.
+ const dropdownOnOpen = wrapper
+ .find(Dropdown)
+ .at(0)
+ .props().onOpen;
+ if (!dropdownOnOpen) {
+ reject();
+ return;
+ }
+ dropdownOnOpen();
+ expect(setEditedCommentKey).toHaveBeenLastCalledWith('');
+
+ // Confirming deletion calls the prop to delete the comment.
+ const dropdownOverlay = shallow(
+ wrapper
+ .find(Dropdown)
+ .at(0)
+ .props().overlay as React.ReactElement<HTMLDivElement>
+ );
+ const deleteButtonOnClick = dropdownOverlay.find(Button).props().onClick;
+ if (!deleteButtonOnClick) {
+ reject();
+ return;
+ }
+
+ deleteButtonOnClick();
+ expect(onDeleteComment).toBeCalledWith('comment-1');
+
+ resolve();
+ });
+});
+
+function shallowRender(props?: Partial<HotspotReviewHistoryProps>) {
return shallow(
<HotspotReviewHistory
- hotspot={hotspot}
+ hotspot={mockHotspot({
+ creationDate: '2018-09-01',
+ changelog: [
+ mockIssueChangelog(),
+ mockIssueChangelog({
+ creationDate: '2018-10-12'
+ })
+ ],
+ comment: [
+ mockHotspotComment({
+ key: 'comment-1',
+ updatable: true
+ }),
+ mockHotspotComment({ key: 'comment-2', user: mockUser({ name: undefined }) }),
+ mockHotspotComment({ key: 'comment-3', user: mockUser({ active: false }) }),
+ mockHotspotComment({ key: 'comment-4' }),
+ mockHotspotComment({ key: 'comment-5' })
+ ]
+ })}
onDeleteComment={jest.fn()}
onEditComment={jest.fn()}
+ onShowFullHistory={jest.fn()}
+ showFullHistory={false}
{...props}
/>
);
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistoryAndComments-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistoryAndComments-test.tsx
index 27f02fac853..b20d07a2e33 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistoryAndComments-test.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistoryAndComments-test.tsx
@@ -50,16 +50,10 @@ it('should render correctly without user', () => {
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 });
+ const wrapper = shallowRender({ hotspot });
mockApi.mockClear();
wrapper.instance().setState({ comment: 'Comment' });
@@ -68,26 +62,11 @@ it('should submit comment', async () => {
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 });
+ const wrapper = shallowRender();
wrapper.instance().setState({ comment: 'Comment' });
wrapper.find('textarea').simulate('change', { target: { value: 'Foo' } });
@@ -97,7 +76,7 @@ it('should change comment', () => {
it('should reset on change hotspot', () => {
const wrapper = shallowRender();
wrapper.setState({ comment: 'NOP' });
- wrapper.setProps({ hotspot: mockHotspot() });
+ wrapper.setProps({ hotspot: mockHotspot({ key: 'other-hotspot' }) });
expect(wrapper.state().comment).toBe('');
});
@@ -122,16 +101,23 @@ it('should edit comment', async () => {
expect(wrapper.instance().props.onCommentUpdate).toBeCalledTimes(1);
});
+it('should correctly toggle the show full history state', () => {
+ const wrapper = shallowRender();
+ expect(wrapper.state().showFullHistory).toBe(false);
+ wrapper
+ .find(HotspotReviewHistory)
+ .props()
+ .onShowFullHistory();
+ expect(wrapper.state().showFullHistory).toBe(true);
+});
+
function shallowRender(props?: Partial<HotspotReviewHistoryAndComments['props']>) {
return shallow<HotspotReviewHistoryAndComments>(
<HotspotReviewHistoryAndComments
commentTextRef={React.createRef()}
- commentVisible={false}
currentUser={mockCurrentUser()}
hotspot={mockHotspot()}
- onCloseComment={jest.fn()}
onCommentUpdate={jest.fn()}
- onOpenComment={jest.fn()}
{...props}
/>
);
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewer-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewer-test.tsx
index 43410b47afe..cff7a9c5058 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewer-test.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewer-test.tsx
@@ -104,27 +104,19 @@ it('should NOT refresh hotspot list on assignee/comment updates', () => {
expect(onUpdateHotspot).not.toHaveBeenCalled();
});
-it('should open comment form when scroll to comment', () => {
+it('should scroll to comment form', () => {
const wrapper = shallowRender();
const mockTextRef = ({ current: { focus: jest.fn() } } as any) as React.RefObject<
HTMLTextAreaElement
>;
wrapper.instance().commentTextRef = mockTextRef;
- wrapper.find(HotspotViewerRenderer).simulate('openComment');
+ wrapper.find(HotspotViewerRenderer).simulate('showCommentForm');
- 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({});
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerRenderer-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerRenderer-test.tsx
index 1a681d315fc..1b97fd6395e 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerRenderer-test.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerRenderer-test.tsx
@@ -69,16 +69,14 @@ function shallowRender(props?: Partial<HotspotViewerRendererProps>) {
branchLike={mockBranch()}
component={mockComponent()}
commentTextRef={React.createRef()}
- commentVisible={false}
currentUser={mockCurrentUser()}
hotspot={mockHotspot()}
hotspotsReviewedMeasure="75"
lastStatusChangedTo={HotspotStatusOption.FIXED}
loading={false}
- onCloseComment={jest.fn()}
onCloseStatusUpdateSuccessModal={jest.fn()}
- onOpenComment={jest.fn()}
onSwitchFilterToStatusOfUpdatedHotspot={jest.fn()}
+ onShowCommentForm={jest.fn()}
onUpdateHotspot={jest.fn()}
securityCategories={{ 'sql-injection': { title: 'SQL injection' } }}
showStatusUpdateSuccessModal={false}
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistory-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistory-test.tsx.snap
index c70652a47a0..2475a3723b9 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistory-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistory-test.tsx.snap
@@ -1,208 +1,620 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should render correctly 1`] = `
+exports[`should render correctly: default 1`] = `
<Fragment>
- <div
- className="padded"
- key="0"
- >
- <div
- className="display-flex-center"
+ <ul>
+ <li
+ className="padded-top padded-bottom"
+ key="0"
>
- <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"
+ <div
+ className="display-flex-center"
+ >
+ <Connect(Avatar)
+ className="little-spacer-right"
+ name="Luke Skywalker"
+ size={20}
+ />
+ <strong>
+ Luke Skywalker
+ </strong>
+ <span
+ className="little-spacer-left little-spacer-right"
+ >
+ -
+ </span>
+ <DateTimeFormatter
+ date="2018-10-12"
+ />
+ </div>
+ <div
+ className="spacer-top"
+ >
+ <IssueChangelogDiff
+ diff={
+ Object {
+ "key": "assign",
+ "newValue": "darth.vader",
+ "oldValue": "luke.skywalker",
+ }
+ }
+ key="0"
+ />
+ </div>
+ </li>
+ <li
+ className="padded-top padded-bottom bordered-top"
+ key="1"
>
- <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 display-flex-space-between"
+ <div
+ className="display-flex-center"
+ >
+ <Connect(Avatar)
+ className="little-spacer-right"
+ name="Luke Skywalker"
+ size={20}
+ />
+ <strong>
+ Luke Skywalker
+ </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": "darth.vader",
+ "oldValue": "luke.skywalker",
+ }
+ }
+ key="0"
+ />
+ </div>
+ </li>
+ <li
+ className="padded-top padded-bottom bordered-top"
+ key="2"
>
<div
- className="markdown"
- dangerouslySetInnerHTML={
- Object {
- "__html": "<strong>TEST</strong>",
+ 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 display-flex-space-between"
+ >
+ <div
+ className="markdown"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "<strong>TEST</strong>",
+ }
}
- }
- />
- <div>
+ />
+ </div>
+ </li>
+ <li
+ className="padded-top padded-bottom bordered-top"
+ key="3"
+ >
+ <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 display-flex-space-between"
+ >
<div
- className="dropdown"
+ className="markdown"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "<strong>TEST</strong>",
+ }
+ }
+ />
+ </div>
+ </li>
+ <li
+ className="padded-top padded-bottom bordered-top"
+ key="4"
+ >
+ <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"
>
- <Toggler
- onRequestClose={[Function]}
- open={false}
- overlay={
- <DropdownOverlay
- placement="bottom-right"
- >
- <HotspotCommentPopup
- markdownComment="*TEST*"
- onCancelEdit={[Function]}
- onCommentEditSubmit={[Function]}
- />
- </DropdownOverlay>
+ 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 display-flex-space-between"
+ >
+ <div
+ className="markdown"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "<strong>TEST</strong>",
}
- >
- <EditButton
- className="button-small"
- onClick={[Function]}
- />
- </Toggler>
- </div>
- <Dropdown
- onOpen={[Function]}
- overlay={
- <div
- className="padded abs-width-150"
- >
- <p>
- issue.comment.delete_confirm_message
- </p>
- <Button
- className="button-red big-spacer-top pull-right"
- onClick={[Function]}
- >
- delete
- </Button>
- </div>
}
- overlayPlacement="bottom-right"
- >
- <DeleteButton
- className="button-small"
- />
- </Dropdown>
- </div>
- </div>
- </div>
- <div
- className="padded bordered-top"
- key="2"
+ />
+ </div>
+ </li>
+ </ul>
+ <ButtonLink
+ className="spacer-top"
+ onClick={[MockFunction]}
>
- <div
- className="display-flex-center"
+ show_all
+ </ButtonLink>
+</Fragment>
+`;
+
+exports[`should render correctly: delete comment overlay 1`] = `
+<div
+ className="padded abs-width-150"
+>
+ <p>
+ issue.comment.delete_confirm_message
+ </p>
+ <Button
+ className="button-red big-spacer-top pull-right"
+ onClick={[Function]}
+ >
+ delete
+ </Button>
+</div>
+`;
+
+exports[`should render correctly: edit comment overlay 1`] = `
+<DropdownOverlay
+ placement="bottom-right"
+>
+ <HotspotCommentPopup
+ markdownComment="*TEST*"
+ onCancelEdit={[Function]}
+ onCommentEditSubmit={[Function]}
+ />
+</DropdownOverlay>
+`;
+
+exports[`should render correctly: show full list 1`] = `
+<Fragment>
+ <ul>
+ <li
+ className="padded-top padded-bottom"
+ key="0"
>
- <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 display-flex-space-between"
+ <div
+ className="display-flex-center"
+ >
+ <Connect(Avatar)
+ className="little-spacer-right"
+ name="Luke Skywalker"
+ size={20}
+ />
+ <strong>
+ Luke Skywalker
+ </strong>
+ <span
+ className="little-spacer-left little-spacer-right"
+ >
+ -
+ </span>
+ <DateTimeFormatter
+ date="2018-10-12"
+ />
+ </div>
+ <div
+ className="spacer-top"
+ >
+ <IssueChangelogDiff
+ diff={
+ Object {
+ "key": "assign",
+ "newValue": "darth.vader",
+ "oldValue": "luke.skywalker",
+ }
+ }
+ key="0"
+ />
+ </div>
+ </li>
+ <li
+ className="padded-top padded-bottom bordered-top"
+ key="1"
>
<div
- className="markdown"
- dangerouslySetInnerHTML={
- Object {
- "__html": "<strong>TEST</strong>",
+ className="display-flex-center"
+ >
+ <Connect(Avatar)
+ className="little-spacer-right"
+ name="Luke Skywalker"
+ size={20}
+ />
+ <strong>
+ Luke Skywalker
+ </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": "darth.vader",
+ "oldValue": "luke.skywalker",
+ }
}
- }
- />
- </div>
- </div>
- <div
- className="padded bordered-top"
- key="3"
- >
- <div
- className="display-flex-center"
+ key="0"
+ />
+ </div>
+ </li>
+ <li
+ className="padded-top padded-bottom 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-10"
+ />
+ </div>
+ <div
+ className="spacer-top display-flex-space-between"
+ >
+ <div
+ className="markdown"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "<strong>TEST</strong>",
+ }
+ }
+ />
+ </div>
+ </li>
+ <li
+ className="padded-top padded-bottom bordered-top"
+ key="3"
+ >
+ <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 display-flex-space-between"
+ >
+ <div
+ className="markdown"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "<strong>TEST</strong>",
+ }
+ }
+ />
+ </div>
+ </li>
+ <li
+ className="padded-top padded-bottom bordered-top"
+ key="4"
+ >
+ <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.review_history.comment_added
+ </span>
+ <span
+ className="little-spacer-left little-spacer-right"
+ >
+ -
+ </span>
+ <DateTimeFormatter
+ date="2018-09-10"
+ />
+ </div>
+ <div
+ className="spacer-top display-flex-space-between"
+ >
+ <div
+ className="markdown"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "<strong>TEST</strong>",
+ }
+ }
+ />
+ </div>
+ </li>
+ <li
+ className="padded-top padded-bottom bordered-top"
+ key="5"
>
- <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"
+ <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 display-flex-space-between"
+ >
+ <div
+ className="markdown"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "<strong>TEST</strong>",
+ }
+ }
+ />
+ </div>
+ </li>
+ <li
+ className="padded-top padded-bottom bordered-top"
+ key="6"
>
- <IssueChangelogDiff
- diff={
- Object {
- "key": "assign",
- "newValue": "me",
- "oldValue": "him",
+ <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 display-flex-space-between"
+ >
+ <div
+ className="markdown"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "<strong>TEST</strong>",
+ }
}
- }
- key="0"
- />
- </div>
- </div>
+ />
+ <div>
+ <div
+ className="dropdown"
+ >
+ <Toggler
+ onRequestClose={[Function]}
+ open={false}
+ overlay={
+ <DropdownOverlay
+ placement="bottom-right"
+ >
+ <HotspotCommentPopup
+ markdownComment="*TEST*"
+ onCancelEdit={[Function]}
+ onCommentEditSubmit={[Function]}
+ />
+ </DropdownOverlay>
+ }
+ >
+ <EditButton
+ className="button-small"
+ onClick={[Function]}
+ />
+ </Toggler>
+ </div>
+ <Dropdown
+ onOpen={[Function]}
+ overlay={
+ <div
+ className="padded abs-width-150"
+ >
+ <p>
+ issue.comment.delete_confirm_message
+ </p>
+ <Button
+ className="button-red big-spacer-top pull-right"
+ onClick={[Function]}
+ >
+ delete
+ </Button>
+ </div>
+ }
+ overlayPlacement="bottom-right"
+ >
+ <DeleteButton
+ className="button-small"
+ />
+ </Dropdown>
+ </div>
+ </div>
+ </li>
+ <li
+ className="padded-top padded-bottom bordered-top"
+ key="7"
+ >
+ <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>
+ </li>
+ </ul>
</Fragment>
`;
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistoryAndComments-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistoryAndComments-test.tsx.snap
index e576a075c66..ee97c8ccccc 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistoryAndComments-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistoryAndComments-test.tsx.snap
@@ -1,225 +1,210 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should render correctly 1`] = `
-<Fragment>
- <h1>
- hotspot.section.activity
- </h1>
+<div
+ className="padded it__hs-review-history"
+>
<div
- className="padded it__hs-review-history"
+ className="big-spacer-top"
>
- <HotspotReviewHistory
- hotspot={
- Object {
- "assignee": "assignee",
- "assigneeUser": Object {
+ <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 "
+ >
+ <FormattingTips
+ className="huge-spacer-bottom"
+ />
+ <div>
+ <Button
+ className="huge-spacer-bottom"
+ id="hotspot-comment-box-submit"
+ onClick={[Function]}
+ >
+ hotspots.comment.submit
+ </Button>
+ </div>
+ </div>
+ </div>
+ <h2
+ className="spacer-top big-spacer-bottom"
+ >
+ hotspot.section.activity
+ </h2>
+ <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 {
+ "key": "hotspot-component",
+ "longName": "Hotspot component long name",
+ "name": "Hotspot Component",
+ "path": "path/to/component",
+ "qualifier": "FIL",
+ },
+ "creationDate": "2013-05-13T17:55:41+0200",
+ "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123",
+ "line": 142,
+ "message": "'3' is a magic number.",
+ "project": Object {
+ "key": "hotspot-component",
+ "longName": "Hotspot component long name",
+ "name": "Hotspot Component",
+ "path": "path/to/component",
+ "qualifier": "TRK",
+ },
+ "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",
},
- "author": "author",
- "authorUser": Object {
+ Object {
"active": true,
"local": true,
"login": "author",
"name": "John Doe",
},
- "canChangeStatus": true,
- "changelog": Array [],
- "comment": Array [],
- "component": Object {
- "key": "hotspot-component",
- "longName": "Hotspot component long name",
- "name": "Hotspot Component",
- "path": "path/to/component",
- "qualifier": "FIL",
- },
- "creationDate": "2013-05-13T17:55:41+0200",
- "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123",
- "line": 142,
- "message": "'3' is a magic number.",
- "project": Object {
- "key": "hotspot-component",
- "longName": "Hotspot component long name",
- "name": "Hotspot Component",
- "path": "path/to/component",
- "qualifier": "TRK",
- },
- "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",
- },
- ],
- }
+ ],
}
- onDeleteComment={[Function]}
- onEditComment={[Function]}
- />
- <hr />
- <div
- className="big-spacer-top"
- >
- <Button
- className="it__hs-add-comment"
- 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 "
- >
- <FormattingTips
- 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>
+ }
+ onDeleteComment={[Function]}
+ onEditComment={[Function]}
+ onShowFullHistory={[Function]}
+ showFullHistory={false}
+ />
+</div>
`;
exports[`should render correctly without user 1`] = `
-<Fragment>
- <h1>
- hotspot.section.activity
- </h1>
- <div
- className="padded it__hs-review-history"
+<div
+ className="padded it__hs-review-history"
+>
+ <h2
+ className="spacer-top big-spacer-bottom"
>
- <HotspotReviewHistory
- hotspot={
- Object {
- "assignee": "assignee",
- "assigneeUser": Object {
+ hotspot.section.activity
+ </h2>
+ <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 {
+ "key": "hotspot-component",
+ "longName": "Hotspot component long name",
+ "name": "Hotspot Component",
+ "path": "path/to/component",
+ "qualifier": "FIL",
+ },
+ "creationDate": "2013-05-13T17:55:41+0200",
+ "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123",
+ "line": 142,
+ "message": "'3' is a magic number.",
+ "project": Object {
+ "key": "hotspot-component",
+ "longName": "Hotspot component long name",
+ "name": "Hotspot Component",
+ "path": "path/to/component",
+ "qualifier": "TRK",
+ },
+ "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",
},
- "author": "author",
- "authorUser": Object {
+ Object {
"active": true,
"local": true,
"login": "author",
"name": "John Doe",
},
- "canChangeStatus": true,
- "changelog": Array [],
- "comment": Array [],
- "component": Object {
- "key": "hotspot-component",
- "longName": "Hotspot component long name",
- "name": "Hotspot Component",
- "path": "path/to/component",
- "qualifier": "FIL",
- },
- "creationDate": "2013-05-13T17:55:41+0200",
- "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123",
- "line": 142,
- "message": "'3' is a magic number.",
- "project": Object {
- "key": "hotspot-component",
- "longName": "Hotspot component long name",
- "name": "Hotspot Component",
- "path": "path/to/component",
- "qualifier": "TRK",
- },
- "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",
- },
- ],
- }
+ ],
}
- onDeleteComment={[Function]}
- onEditComment={[Function]}
- />
- </div>
-</Fragment>
+ }
+ onDeleteComment={[Function]}
+ onEditComment={[Function]}
+ onShowFullHistory={[Function]}
+ showFullHistory={false}
+ />
+</div>
`;
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewer-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewer-test.tsx.snap
index 44e0fda6ace..e9d02356247 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewer-test.tsx.snap
@@ -7,7 +7,6 @@ exports[`should render correctly 1`] = `
"current": null,
}
}
- commentVisible={false}
component={
Object {
"breadcrumbs": Array [],
@@ -31,9 +30,8 @@ exports[`should render correctly 1`] = `
}
}
loading={true}
- onCloseComment={[Function]}
onCloseStatusUpdateSuccessModal={[Function]}
- onOpenComment={[Function]}
+ onShowCommentForm={[Function]}
onSwitchFilterToStatusOfUpdatedHotspot={[Function]}
onUpdateHotspot={[Function]}
securityCategories={
@@ -54,7 +52,6 @@ exports[`should render correctly 2`] = `
"current": null,
}
}
- commentVisible={false}
component={
Object {
"breadcrumbs": Array [],
@@ -83,9 +80,8 @@ exports[`should render correctly 2`] = `
}
}
loading={false}
- onCloseComment={[Function]}
onCloseStatusUpdateSuccessModal={[Function]}
- onOpenComment={[Function]}
+ onShowCommentForm={[Function]}
onSwitchFilterToStatusOfUpdatedHotspot={[Function]}
onUpdateHotspot={[Function]}
securityCategories={
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap
index 1180deb78c9..ea3ef767b32 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap
@@ -451,7 +451,6 @@ exports[`should render correctly: anonymous user 1`] = `
"current": null,
}
}
- commentVisible={false}
currentUser={
Object {
"isLoggedIn": false,
@@ -528,9 +527,7 @@ exports[`should render correctly: anonymous user 1`] = `
],
}
}
- onCloseComment={[MockFunction]}
onCommentUpdate={[MockFunction]}
- onOpenComment={[MockFunction]}
/>
</div>
</DeferredSpinner>
@@ -987,7 +984,6 @@ exports[`should render correctly: assignee without name 1`] = `
"current": null,
}
}
- commentVisible={false}
currentUser={
Object {
"isLoggedIn": false,
@@ -1064,9 +1060,7 @@ exports[`should render correctly: assignee without name 1`] = `
],
}
}
- onCloseComment={[MockFunction]}
onCommentUpdate={[MockFunction]}
- onOpenComment={[MockFunction]}
/>
</div>
</DeferredSpinner>
@@ -1523,7 +1517,6 @@ exports[`should render correctly: default 1`] = `
"current": null,
}
}
- commentVisible={false}
currentUser={
Object {
"isLoggedIn": false,
@@ -1600,9 +1593,7 @@ exports[`should render correctly: default 1`] = `
],
}
}
- onCloseComment={[MockFunction]}
onCommentUpdate={[MockFunction]}
- onOpenComment={[MockFunction]}
/>
</div>
</DeferredSpinner>
@@ -2059,7 +2050,6 @@ exports[`should render correctly: deleted assignee 1`] = `
"current": null,
}
}
- commentVisible={false}
currentUser={
Object {
"isLoggedIn": false,
@@ -2136,9 +2126,7 @@ exports[`should render correctly: deleted assignee 1`] = `
],
}
}
- onCloseComment={[MockFunction]}
onCommentUpdate={[MockFunction]}
- onOpenComment={[MockFunction]}
/>
</div>
</DeferredSpinner>
@@ -2608,7 +2596,6 @@ exports[`should render correctly: show success modal 1`] = `
"current": null,
}
}
- commentVisible={false}
currentUser={
Object {
"isLoggedIn": false,
@@ -2685,9 +2672,7 @@ exports[`should render correctly: show success modal 1`] = `
],
}
}
- onCloseComment={[MockFunction]}
onCommentUpdate={[MockFunction]}
- onOpenComment={[MockFunction]}
/>
</div>
</DeferredSpinner>
@@ -3144,7 +3129,6 @@ exports[`should render correctly: unassigned 1`] = `
"current": null,
}
}
- commentVisible={false}
currentUser={
Object {
"isLoggedIn": false,
@@ -3221,9 +3205,7 @@ exports[`should render correctly: unassigned 1`] = `
],
}
}
- onCloseComment={[MockFunction]}
onCommentUpdate={[MockFunction]}
- onOpenComment={[MockFunction]}
/>
</div>
</DeferredSpinner>
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/utils.ts b/server/sonar-web/src/main/js/apps/security-hotspots/utils.ts
index 7f7db63d9dd..ecde73f591e 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/utils.ts
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/utils.ts
@@ -148,7 +148,7 @@ export function getHotspotReviewHistory(hotspot: Hotspot): ReviewHistoryElement[
);
}
- return sortBy(history, elt => elt.date);
+ return sortBy(history, elt => elt.date).reverse();
}
const STATUS_AND_RESOLUTION_TO_STATUS_OPTION = {
diff --git a/server/sonar-web/src/main/js/helpers/mocks/issues.ts b/server/sonar-web/src/main/js/helpers/mocks/issues.ts
index 9c128954725..034eb0af139 100644
--- a/server/sonar-web/src/main/js/helpers/mocks/issues.ts
+++ b/server/sonar-web/src/main/js/helpers/mocks/issues.ts
@@ -26,3 +26,20 @@ export function mockReferencedRule(overrides: Partial<ReferencedRule> = {}): Ref
...overrides
};
}
+
+export function mockIssueChangelog(overrides: Partial<T.IssueChangelog> = {}): T.IssueChangelog {
+ return {
+ creationDate: '2018-10-01',
+ isUserActive: true,
+ user: 'luke.skywalker',
+ userName: 'Luke Skywalker',
+ diffs: [
+ {
+ key: 'assign',
+ newValue: 'darth.vader',
+ oldValue: 'luke.skywalker'
+ }
+ ],
+ ...overrides
+ };
+}
diff --git a/server/sonar-web/src/main/js/helpers/mocks/security-hotspots.ts b/server/sonar-web/src/main/js/helpers/mocks/security-hotspots.ts
index 74a2b80a647..60d2727fd03 100644
--- a/server/sonar-web/src/main/js/helpers/mocks/security-hotspots.ts
+++ b/server/sonar-web/src/main/js/helpers/mocks/security-hotspots.ts
@@ -21,6 +21,7 @@ import { ComponentQualifier } from '../../types/component';
import { Standards } from '../../types/security';
import {
Hotspot,
+ HotspotComment,
HotspotComponent,
HotspotResolution,
HotspotRule,
@@ -94,6 +95,19 @@ export function mockHotspotComponent(overrides?: Partial<HotspotComponent>): Hot
};
}
+export function mockHotspotComment(overrides?: Partial<HotspotComment>): HotspotComment {
+ return {
+ key: 'comment-1',
+ createdAt: '2018-09-10',
+ htmlText: '<strong>TEST</strong>',
+ markdown: '*TEST*',
+ updatable: false,
+ login: 'dude-2',
+ user: mockUser({ login: 'dude-2' }),
+ ...overrides
+ };
+}
+
export function mockHotspotRule(overrides?: Partial<HotspotRule>): HotspotRule {
return {
key: 'squid:S2077',
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index ba0dde5de24..dd22228b224 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -184,6 +184,7 @@ set=Set
set_up=Set Up
severity=Severity
shared=Shared
+show_all=Show all
start_date=Start Date
x_show={0} shown
x_selected={0} selected
@@ -784,7 +785,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:
+hotspot.section.activity=Recent activity:
hotspots.reviewed.tooltip=Percentage of Security Hotspots reviewed (fixed or safe) among all non-closed Security Hotspots.
hotspots.review_hotspot=Review Hotspot