import throwGlobalError from '../app/utils/throwGlobalError';
import { BranchParameters } from '../types/branch-like';
import {
- DetailedHotspot,
+ Hotspot,
HotspotAssignRequest,
HotspotResolution,
HotspotSearchResponse,
return getJSON('/api/hotspots/search', data).catch(throwGlobalError);
}
-export function getSecurityHotspotDetails(securityHotspotKey: string): Promise<DetailedHotspot> {
+export function getSecurityHotspotDetails(securityHotspotKey: string): Promise<Hotspot> {
return getJSON('/api/hotspots/show', { hotspot: securityHotspotKey }).catch(throwGlobalError);
}
background-color: var(--alertBackgroundError);
color: var(--alertTextError);
}
+
+.counter-badge {
+ color: var(--badgeBlueColor);
+ background-color: var(--badgeBlueBackground);
+ padding: calc(var(--gridSize) / 2) var(--gridSize);
+ border-radius: 1em;
+}
+
+.counter-badge:empty {
+ display: none;
+}
alertTextInfo: '#0e516f',
alertIconInfo: '#0271b9',
+ // badge
+ badgeBlueBackground: '#2E7CB5',
+ badgeBlueColor: '#FFFFFF',
+
// alm
azure: '#0078d7',
bitbucket: '#0052CC',
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { mockRawHotspot } from '../../../helpers/mocks/security-hotspots';
-import { RiskExposure } from '../../../types/security-hotspots';
-import { groupByCategory, mapRules, sortHotspots } from '../utils';
+import { mockHotspot, mockRawHotspot } from '../../../helpers/mocks/security-hotspots';
+import { ReviewHistoryType, RiskExposure } from '../../../types/security-hotspots';
+import { getHotspotReviewHistory, groupByCategory, mapRules, sortHotspots } from '../utils';
const hotspots = [
mockRawHotspot({
});
});
});
+
+describe('getHotspotReviewHistory', () => {
+ it('should properly create the review history', () => {
+ const changelogElement: T.IssueChangelog = {
+ creationDate: '2018-10-01',
+ isUserActive: true,
+ user: 'me',
+ userName: 'me-name',
+ diffs: [
+ {
+ key: 'assign',
+ newValue: 'me',
+ oldValue: 'him'
+ }
+ ]
+ };
+ const hotspot = mockHotspot({
+ creationDate: '2018-09-01',
+ changelog: [changelogElement]
+ });
+ const history = getHotspotReviewHistory(hotspot);
+
+ expect(history.length).toBe(2);
+ expect(history[0]).toEqual(
+ expect.objectContaining({
+ type: ReviewHistoryType.Creation,
+ date: hotspot.creationDate,
+ user: {
+ avatar: hotspot.author.avatar,
+ name: hotspot.author.name,
+ active: hotspot.author.active
+ }
+ })
+ );
+ expect(history[1]).toEqual(
+ expect.objectContaining({
+ type: ReviewHistoryType.Diff,
+ date: changelogElement.creationDate,
+ user: {
+ avatar: changelogElement.avatar,
+ name: changelogElement.userName,
+ active: changelogElement.isUserActive
+ },
+ diffs: changelogElement.diffs
+ })
+ );
+ });
+});
import DropdownIcon from 'sonar-ui-common/components/icons/DropdownIcon';
import { PopupPlacement } from 'sonar-ui-common/components/ui/popups';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import { DetailedHotspot, HotspotUpdateFields } from '../../../types/security-hotspots';
+import { Hotspot, HotspotUpdateFields } from '../../../types/security-hotspots';
import HotspotActionsForm from './HotspotActionsForm';
export interface HotspotActionsProps {
- hotspot: DetailedHotspot;
+ hotspot: Hotspot;
onSubmit: (hotspot: HotspotUpdateFields) => void;
}
onClick={() => setExpanded(!expanded)}>
<strong className="flex-1">{category.title}</strong>
<span>
- <span className="hotspot-counter">{hotspots.length}</span>
+ <span className="counter-badge">{hotspots.length}</span>
{expanded ? (
<ChevronUpIcon className="big-spacer-left" />
) : (
cursor: unset;
}
-.hotspot-counter {
- color: var(--baseFontColor);
- background-color: var(--gray94);
- border-radius: 50%;
- padding: calc(var(--gridSize) / 2) var(--gridSize);
-}
-
.hotspot-risk-badge {
color: white;
text-transform: uppercase;
import { locationsByLine } from '../../../components/SourceViewer/helpers/indexing';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { BranchLike } from '../../../types/branch-like';
-import { DetailedHotspot } from '../../../types/security-hotspots';
+import { Hotspot } from '../../../types/security-hotspots';
import { constructSourceViewerFile } from '../utils';
import HotspotSnippetContainerRenderer from './HotspotSnippetContainerRenderer';
interface Props {
branchLike?: BranchLike;
- hotspot: DetailedHotspot;
+ hotspot: Hotspot;
}
interface State {
import { SourceViewerContext } from '../../../components/SourceViewer/SourceViewerContext';
import SourceViewerHeaderSlim from '../../../components/SourceViewer/SourceViewerHeaderSlim';
import { BranchLike } from '../../../types/branch-like';
-import { DetailedHotspot } from '../../../types/security-hotspots';
+import { Hotspot } from '../../../types/security-hotspots';
import SnippetViewer from '../../issues/crossComponentSourceViewer/SnippetViewer';
export interface HotspotSnippetContainerRendererProps {
branchLike?: BranchLike;
highlightedSymbols: string[];
- hotspot: DetailedHotspot;
+ hotspot: Hotspot;
lastLine?: number;
loading: boolean;
locations: { [line: number]: T.LinearIssueLocation[] };
import * as React from 'react';
import { getSecurityHotspotDetails } from '../../../api/security-hotspots';
import { BranchLike } from '../../../types/branch-like';
-import {
- DetailedHotspot,
- HotspotUpdate,
- HotspotUpdateFields
-} from '../../../types/security-hotspots';
+import { Hotspot, HotspotUpdate, HotspotUpdateFields } from '../../../types/security-hotspots';
import HotspotViewerRenderer from './HotspotViewerRenderer';
interface Props {
}
interface State {
- hotspot?: DetailedHotspot;
+ hotspot?: Hotspot;
loading: boolean;
}
export default class HotspotViewer extends React.PureComponent<Props, State> {
mounted = false;
+ state: State = { loading: false };
- componentWillMount() {
+ componentDidMount() {
this.mounted = true;
this.fetchHotspot();
}
fetchHotspot() {
this.setState({ loading: true });
return getSecurityHotspotDetails(this.props.hotspotKey)
- .then(hotspot => this.mounted && this.setState({ hotspot }))
- .finally(() => this.mounted && this.setState({ loading: false }));
+ .then(hotspot => this.mounted && this.setState({ hotspot, loading: false }))
+ .catch(() => this.mounted && this.setState({ loading: false }));
}
handleHotspotUpdate = (data: HotspotUpdateFields) => {
import { withCurrentUser } from '../../../components/hoc/withCurrentUser';
import { isLoggedIn } from '../../../helpers/users';
import { BranchLike } from '../../../types/branch-like';
-import { DetailedHotspot, HotspotUpdateFields } from '../../../types/security-hotspots';
+import { Hotspot, HotspotUpdateFields } from '../../../types/security-hotspots';
import HotspotActions from './HotspotActions';
import HotspotSnippetContainer from './HotspotSnippetContainer';
import HotspotViewerTabs from './HotspotViewerTabs';
export interface HotspotViewerRendererProps {
branchLike?: BranchLike;
currentUser: T.CurrentUser;
- hotspot?: DetailedHotspot;
+ hotspot?: Hotspot;
loading: boolean;
onUpdateHotspot: (hotspot: HotspotUpdateFields) => void;
securityCategories: T.StandardSecurityCategories;
)}
</div>
<div className="text-muted">
- <span>{translate('hotspot.category')}</span>
+ <span>{translate('category')}:</span>
<span className="little-spacer-left">
{securityCategories[hotspot.rule.securityCategory].title}
</span>
</div>
</div>
<div className="huge-spacer-bottom">
- <span>{translate('hotspot.status')}</span>
+ <span>{translate('status')}:</span>
<span className="badge little-spacer-left">
{translate('hotspot.status', hotspot.resolution || hotspot.status)}
</span>
{hotspot.assignee && hotspot.assignee.name && (
<>
- <span className="huge-spacer-left">{translate('hotspot.assigned_to')}</span>
+ <span className="huge-spacer-left">{translate('assigned_to')}:</span>
<strong className="little-spacer-left">
{hotspot.assignee.active
? hotspot.assignee.name
--- /dev/null
+/*
+ * 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 { 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 { ReviewHistoryElement, ReviewHistoryType } from '../../../types/security-hotspots';
+
+export interface HotspotViewerReviewHistoryTabProps {
+ history: ReviewHistoryElement[];
+}
+
+export default function HotspotViewerReviewHistoryTab(props: HotspotViewerReviewHistoryTabProps) {
+ const { history } = props;
+
+ return (
+ <>
+ {history.map((elt, i) => (
+ <React.Fragment key={`${elt.user.name}-${elt.date}`}>
+ {i > 0 && <hr />}
+ <div>
+ <div className="display-flex-center">
+ {elt.user.name && (
+ <>
+ <Avatar
+ className="little-spacer-right"
+ hash={elt.user.avatar}
+ name={elt.user.name}
+ size={20}
+ />
+ <strong>
+ {elt.user.active
+ ? elt.user.name
+ : translateWithParameters('user.x_deleted', elt.user.name)}
+ </strong>
+ {elt.type === ReviewHistoryType.Creation && (
+ <span className="little-spacer-left">
+ {translate('hotspots.tabs.review_history.created')}
+ </span>
+ )}
+ <span className="little-spacer-left little-spacer-right">-</span>
+ </>
+ )}
+ <DateTimeFormatter date={elt.date} />
+ </div>
+
+ {elt.type === ReviewHistoryType.Diff && elt.diffs && (
+ <div className="spacer-top">
+ {elt.diffs.map(diff => (
+ <IssueChangelogDiff
+ diff={diff}
+ key={`${diff.key}-${diff.oldValue}-${diff.newValue}`}
+ />
+ ))}
+ </div>
+ )}
+ </div>
+ </React.Fragment>
+ ))}
+ </>
+ );
+}
import * as React from 'react';
import BoxedTabs from 'sonar-ui-common/components/controls/BoxedTabs';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import { DetailedHotspot } from '../../../types/security-hotspots';
+import { Hotspot } from '../../../types/security-hotspots';
+import { getHotspotReviewHistory } from '../utils';
+import HotspotViewerReviewHistoryTab from './HotspotViewerReviewHistoryTab';
export interface HotspotViewerTabsProps {
- hotspot: DetailedHotspot;
+ hotspot: Hotspot;
}
export enum Tabs {
RiskDescription = 'risk',
VulnerabilityDescription = 'vulnerability',
- FixRecommendation = 'fix'
+ FixRecommendation = 'fix',
+ ReviewHistory = 'review'
}
export default function HotspotViewerTabs(props: HotspotViewerTabsProps) {
const { hotspot } = props;
- const [currentTab, setCurrentTab] = React.useState(Tabs.RiskDescription);
+ const [currentTabKey, setCurrentTabKey] = React.useState(Tabs.RiskDescription);
+ const hotspotReviewHistory = React.useMemo(() => getHotspotReviewHistory(hotspot), [hotspot]);
- const tabs = {
- [Tabs.RiskDescription]: {
- title: translate('hotspot.tabs.risk_description'),
+ const tabs = [
+ {
+ key: Tabs.RiskDescription,
+ label: translate('hotspots.tabs.risk_description'),
content: hotspot.rule.riskDescription || ''
},
- [Tabs.VulnerabilityDescription]: {
- title: translate('hotspot.tabs.vulnerability_description'),
+ {
+ key: Tabs.VulnerabilityDescription,
+ label: translate('hotspots.tabs.vulnerability_description'),
content: hotspot.rule.vulnerabilityDescription || ''
},
- [Tabs.FixRecommendation]: {
- title: translate('hotspot.tabs.fix_recommendations'),
+ {
+ key: Tabs.FixRecommendation,
+ label: translate('hotspots.tabs.fix_recommendations'),
content: hotspot.rule.fixRecommendations || ''
+ },
+ {
+ key: Tabs.ReviewHistory,
+ label: (
+ <>
+ <span>{translate('hotspots.tabs.review_history')}</span>
+ <span className="counter-badge spacer-left">{hotspotReviewHistory.length}</span>
+ </>
+ ),
+ content: hotspotReviewHistory.length > 0 && (
+ <HotspotViewerReviewHistoryTab history={hotspotReviewHistory} />
+ )
}
- };
-
- const tabsToDisplay = Object.values(Tabs)
- .filter(tab => Boolean(tabs[tab].content))
- .map(tab => ({ key: tab, label: tabs[tab].title }));
+ ].filter(tab => Boolean(tab.content));
- if (tabsToDisplay.length === 0) {
+ if (tabs.length === 0) {
return null;
}
- if (!tabsToDisplay.find(tab => tab.key === currentTab)) {
- setCurrentTab(tabsToDisplay[0].key);
- }
+ const currentTab = tabs.find(tab => tab.key === currentTabKey) || tabs[0];
return (
<>
- <BoxedTabs onSelect={tab => setCurrentTab(tab)} selected={currentTab} tabs={tabsToDisplay} />
- <div
- className="boxed-group markdown big-padded"
- dangerouslySetInnerHTML={{ __html: sanitize(tabs[currentTab].content) }}
+ <BoxedTabs
+ onSelect={tabKey => setCurrentTabKey(tabKey)}
+ selected={currentTabKey}
+ tabs={tabs}
/>
+ <div className="boxed-group big-padded">
+ {typeof currentTab.content === 'string' ? (
+ <div
+ className="markdown"
+ dangerouslySetInnerHTML={{ __html: sanitize(currentTab.content) }}
+ />
+ ) : (
+ <>{currentTab.content}</>
+ )}
+ </div>
</>
);
}
import * as React from 'react';
import { Button } from 'sonar-ui-common/components/controls/buttons';
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
-import { mockDetailledHotspot } from '../../../../helpers/mocks/security-hotspots';
+import { mockHotspot } from '../../../../helpers/mocks/security-hotspots';
import { HotspotStatus } from '../../../../types/security-hotspots';
import HotspotActions, { HotspotActionsProps } from '../HotspotActions';
function shallowRender(props: Partial<HotspotActionsProps> = {}) {
return shallow(
<HotspotActions
- hotspot={mockDetailledHotspot({ key: 'key', status: HotspotStatus.TO_REVIEW })}
+ hotspot={mockHotspot({ key: 'key', status: HotspotStatus.TO_REVIEW })}
onSubmit={jest.fn()}
{...props}
/>
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { getSources } from '../../../../api/components';
import { mockBranch } from '../../../../helpers/mocks/branch-like';
-import { mockDetailledHotspot } from '../../../../helpers/mocks/security-hotspots';
+import { mockHotspot } from '../../../../helpers/mocks/security-hotspots';
import { mockSourceLine } from '../../../../helpers/testMocks';
import HotspotSnippetContainer from '../HotspotSnippetContainer';
import HotspotSnippetContainerRenderer from '../HotspotSnippetContainerRenderer';
range(5, 18).map(line => mockSourceLine({ line }))
);
- const hotspot = mockDetailledHotspot({
+ const hotspot = mockHotspot({
textRange: { startLine: 10, endLine: 11, startOffset: 0, endOffset: 12 }
});
range(5, 15).map(line => mockSourceLine({ line }))
);
- const hotspot = mockDetailledHotspot({
+ const hotspot = mockHotspot({
textRange: { startLine: 10, endLine: 11, startOffset: 0, endOffset: 12 }
});
);
});
- const hotspot = mockDetailledHotspot({
+ const hotspot = mockHotspot({
textRange: { startLine: 10, endLine: 11, startOffset: 0, endOffset: 12 }
});
function shallowRender(props?: Partial<HotspotSnippetContainer['props']>) {
return shallow<HotspotSnippetContainer>(
- <HotspotSnippetContainer branchLike={branch} hotspot={mockDetailledHotspot()} {...props} />
+ <HotspotSnippetContainer branchLike={branch} hotspot={mockHotspot()} {...props} />
);
}
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockMainBranch } from '../../../../helpers/mocks/branch-like';
-import { mockDetailledHotspot } from '../../../../helpers/mocks/security-hotspots';
+import { mockHotspot } from '../../../../helpers/mocks/security-hotspots';
import { mockSourceLine, mockSourceViewerFile } from '../../../../helpers/testMocks';
import HotspotSnippetContainerRenderer, {
HotspotSnippetContainerRendererProps
<HotspotSnippetContainerRenderer
branchLike={mockMainBranch()}
highlightedSymbols={[]}
- hotspot={mockDetailledHotspot()}
+ hotspot={mockHotspot()}
lastLine={undefined}
linePopup={undefined}
loading={false}
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { mockDetailledHotspot } from '../../../../helpers/mocks/security-hotspots';
+import { mockHotspot } from '../../../../helpers/mocks/security-hotspots';
import { mockCurrentUser, mockLoggedInUser, mockUser } from '../../../../helpers/testMocks';
import { HotspotViewerRenderer, HotspotViewerRendererProps } from '../HotspotViewerRenderer';
const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot();
expect(shallowRender({ hotspot: undefined })).toMatchSnapshot('no hotspot');
+ expect(shallowRender({ hotspot: mockHotspot({ assignee: undefined }) })).toMatchSnapshot(
+ 'unassigned'
+ );
expect(
- shallowRender({ hotspot: mockDetailledHotspot({ assignee: mockUser({ active: false }) }) })
+ shallowRender({ hotspot: mockHotspot({ assignee: mockUser({ active: false }) }) })
).toMatchSnapshot('deleted assignee');
+ expect(
+ shallowRender({
+ hotspot: mockHotspot({
+ assignee: mockUser({ name: undefined, login: 'assignee_login' })
+ })
+ })
+ ).toMatchSnapshot('assignee without name');
expect(shallowRender()).toMatchSnapshot('anonymous user');
expect(shallowRender({ currentUser: mockLoggedInUser() })).toMatchSnapshot('user logged in');
});
return shallow(
<HotspotViewerRenderer
currentUser={mockCurrentUser()}
- hotspot={mockDetailledHotspot()}
+ hotspot={mockHotspot()}
loading={false}
onUpdateHotspot={jest.fn()}
securityCategories={{ 'sql-injection': { title: 'SQL injection' } }}
--- /dev/null
+/*
+ * 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 { mockHotspotReviewHistoryElement } from '../../../../helpers/mocks/security-hotspots';
+import { mockUser } from '../../../../helpers/testMocks';
+import { ReviewHistoryType } from '../../../../types/security-hotspots';
+import HotspotViewerReviewHistoryTab, {
+ HotspotViewerReviewHistoryTabProps
+} from '../HotspotViewerReviewHistoryTab';
+
+it('should render correctly', () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+});
+
+function shallowRender(props?: Partial<HotspotViewerReviewHistoryTabProps>) {
+ return shallow(
+ <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' }
+ ]
+ })
+ ]}
+ {...props}
+ />
+ );
+}
import { shallow } from 'enzyme';
import * as React from 'react';
import BoxedTabs from 'sonar-ui-common/components/controls/BoxedTabs';
-import {
- mockDetailledHotspot,
- mockDetailledHotspotRule
-} from '../../../../helpers/mocks/security-hotspots';
+import { mockHotspot, mockHotspotRule } from '../../../../helpers/mocks/security-hotspots';
import HotspotViewerTabs, { HotspotViewerTabsProps, Tabs } from '../HotspotViewerTabs';
it('should render correctly', () => {
onSelect(Tabs.FixRecommendation);
expect(wrapper).toMatchSnapshot('fix');
+
+ onSelect(Tabs.ReviewHistory);
+ expect(wrapper).toMatchSnapshot('review');
}
expect(
shallowRender({
- hotspot: mockDetailledHotspot({
- rule: mockDetailledHotspotRule({ riskDescription: undefined })
+ hotspot: mockHotspot({
+ rule: mockHotspotRule({ riskDescription: undefined })
})
})
).toMatchSnapshot('empty tab');
expect(
shallowRender({
- hotspot: mockDetailledHotspot({
- rule: mockDetailledHotspotRule({
+ hotspot: mockHotspot({
+ creationDate: undefined,
+ rule: mockHotspotRule({
riskDescription: undefined,
fixRecommendations: undefined,
vulnerabilityDescription: undefined
});
function shallowRender(props?: Partial<HotspotViewerTabsProps>) {
- return shallow(<HotspotViewerTabs hotspot={mockDetailledHotspot()} {...props} />);
+ return shallow(<HotspotViewerTabs hotspot={mockHotspot()} {...props} />);
}
</strong>
<span>
<span
- className="hotspot-counter"
+ className="counter-badge"
>
1
</span>
</strong>
<span>
<span
- className="hotspot-counter"
+ className="counter-badge"
>
1
</span>
</strong>
<span>
<span
- className="hotspot-counter"
+ className="counter-badge"
>
2
</span>
"login": "john.doe",
"name": "John Doe",
},
+ "changelog": Array [],
"component": Object {
"breadcrumbs": Array [],
"key": "my-project",
"login": "john.doe",
"name": "John Doe",
},
+ "changelog": Array [],
"component": Object {
"breadcrumbs": Array [],
"key": "my-project",
className="text-muted"
>
<span>
- hotspot.category
+ category
+ :
</span>
<span
className="little-spacer-left"
className="huge-spacer-bottom"
>
<span>
- hotspot.status
+ status
+ :
</span>
<span
className="badge little-spacer-left"
<span
className="huge-spacer-left"
>
- hotspot.assigned_to
+ assigned_to
+ :
</span>
<strong
className="little-spacer-left"
"login": "john.doe",
"name": "John Doe",
},
+ "changelog": Array [],
"component": Object {
"breadcrumbs": Array [],
"key": "my-project",
"login": "john.doe",
"name": "John Doe",
},
+ "changelog": Array [],
"component": Object {
"breadcrumbs": Array [],
"key": "my-project",
className="text-muted"
>
<span>
- hotspot.category
+ category
+ :
</span>
<span
className="little-spacer-left"
className="huge-spacer-bottom"
>
<span>
- hotspot.status
+ status
+ :
</span>
<span
className="badge little-spacer-left"
<span
className="huge-spacer-left"
>
- hotspot.assigned_to
+ assigned_to
+ :
</span>
<strong
className="little-spacer-left"
"login": "john.doe",
"name": "John Doe",
},
+ "changelog": Array [],
"component": Object {
"breadcrumbs": Array [],
"key": "my-project",
"login": "john.doe",
"name": "John Doe",
},
+ "changelog": 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",
+ }
+ }
+ />
+ </div>
+</DeferredSpinner>
+`;
+
+exports[`should render correctly: assignee without name 1`] = `
+<DeferredSpinner
+ loading={false}
+ timeout={100}
+>
+ <div
+ className="big-padded"
+ >
+ <div
+ className="big-spacer-bottom"
+ >
+ <div
+ className="display-flex-space-between"
+ >
+ <h1>
+ '3' is a magic number.
+ </h1>
+ </div>
+ <div
+ className="text-muted"
+ >
+ <span>
+ category
+ :
+ </span>
+ <span
+ className="little-spacer-left"
+ >
+ SQL injection
+ </span>
+ </div>
+ </div>
+ <div
+ className="huge-spacer-bottom"
+ >
+ <span>
+ status
+ :
+ </span>
+ <span
+ className="badge little-spacer-left"
+ >
+ hotspot.status.FIXED
+ </span>
+ </div>
+ <HotspotSnippetContainer
+ hotspot={
+ Object {
+ "assignee": Object {
+ "active": true,
+ "local": true,
+ "login": "assignee_login",
+ "name": undefined,
+ },
+ "author": Object {
+ "active": true,
+ "local": true,
+ "login": "john.doe",
+ "name": "John Doe",
+ },
+ "changelog": 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",
+ }
+ }
+ />
+ <HotspotViewerTabs
+ hotspot={
+ Object {
+ "assignee": Object {
+ "active": true,
+ "local": true,
+ "login": "assignee_login",
+ "name": undefined,
+ },
+ "author": Object {
+ "active": true,
+ "local": true,
+ "login": "john.doe",
+ "name": "John Doe",
+ },
+ "changelog": Array [],
"component": Object {
"breadcrumbs": Array [],
"key": "my-project",
className="text-muted"
>
<span>
- hotspot.category
+ category
+ :
</span>
<span
className="little-spacer-left"
className="huge-spacer-bottom"
>
<span>
- hotspot.status
+ status
+ :
</span>
<span
className="badge little-spacer-left"
<span
className="huge-spacer-left"
>
- hotspot.assigned_to
+ assigned_to
+ :
</span>
<strong
className="little-spacer-left"
"login": "john.doe",
"name": "John Doe",
},
+ "changelog": Array [],
"component": Object {
"breadcrumbs": Array [],
"key": "my-project",
"login": "john.doe",
"name": "John Doe",
},
+ "changelog": Array [],
"component": Object {
"breadcrumbs": Array [],
"key": "my-project",
/>
`;
+exports[`should render correctly: unassigned 1`] = `
+<DeferredSpinner
+ loading={false}
+ timeout={100}
+>
+ <div
+ className="big-padded"
+ >
+ <div
+ className="big-spacer-bottom"
+ >
+ <div
+ className="display-flex-space-between"
+ >
+ <h1>
+ '3' is a magic number.
+ </h1>
+ </div>
+ <div
+ className="text-muted"
+ >
+ <span>
+ category
+ :
+ </span>
+ <span
+ className="little-spacer-left"
+ >
+ SQL injection
+ </span>
+ </div>
+ </div>
+ <div
+ className="huge-spacer-bottom"
+ >
+ <span>
+ status
+ :
+ </span>
+ <span
+ className="badge little-spacer-left"
+ >
+ hotspot.status.FIXED
+ </span>
+ </div>
+ <HotspotSnippetContainer
+ hotspot={
+ Object {
+ "assignee": undefined,
+ "author": Object {
+ "active": true,
+ "local": true,
+ "login": "john.doe",
+ "name": "John Doe",
+ },
+ "changelog": 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",
+ }
+ }
+ />
+ <HotspotViewerTabs
+ hotspot={
+ Object {
+ "assignee": undefined,
+ "author": Object {
+ "active": true,
+ "local": true,
+ "login": "john.doe",
+ "name": "John Doe",
+ },
+ "changelog": 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",
+ }
+ }
+ />
+ </div>
+</DeferredSpinner>
+`;
+
exports[`should render correctly: user logged in 1`] = `
<DeferredSpinner
loading={false}
"login": "john.doe",
"name": "John Doe",
},
+ "changelog": Array [],
"component": Object {
"breadcrumbs": Array [],
"key": "my-project",
className="text-muted"
>
<span>
- hotspot.category
+ category
+ :
</span>
<span
className="little-spacer-left"
className="huge-spacer-bottom"
>
<span>
- hotspot.status
+ status
+ :
</span>
<span
className="badge little-spacer-left"
<span
className="huge-spacer-left"
>
- hotspot.assigned_to
+ assigned_to
+ :
</span>
<strong
className="little-spacer-left"
"login": "john.doe",
"name": "John Doe",
},
+ "changelog": Array [],
"component": Object {
"breadcrumbs": Array [],
"key": "my-project",
"login": "john.doe",
"name": "John Doe",
},
+ "changelog": Array [],
"component": Object {
"breadcrumbs": Array [],
"key": "my-project",
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Fragment>
+ <div>
+ <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>
+ <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>
+ <div
+ className="display-flex-center"
+ >
+ <DateTimeFormatter
+ date="2019-09-13T17:55:42+0200"
+ />
+ </div>
+ </div>
+ <hr />
+ <div>
+ <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="test-old-new"
+ />
+ <IssueChangelogDiff
+ diff={
+ Object {
+ "key": "test-1",
+ "newValue": "new-1",
+ "oldValue": "old-1",
+ }
+ }
+ key="test-1-old-1-new-1"
+ />
+ </div>
+ </div>
+</Fragment>
+`;
<Fragment>
<BoxedTabs
onSelect={[Function]}
- selected="vulnerability"
+ selected="risk"
tabs={
Array [
Object {
+ "content": "<p>This a <strong>strong</strong> message about vulnerability !</p>",
"key": "vulnerability",
- "label": "hotspot.tabs.vulnerability_description",
+ "label": "hotspots.tabs.vulnerability_description",
},
Object {
+ "content": "<p>This a <strong>strong</strong> message about fixing !</p>",
"key": "fix",
- "label": "hotspot.tabs.fix_recommendations",
+ "label": "hotspots.tabs.fix_recommendations",
+ },
+ Object {
+ "content": <HotspotViewerReviewHistoryTab
+ history={
+ Array [
+ Object {
+ "date": "2013-05-13T17:55:41+0200",
+ "type": 0,
+ "user": Object {
+ "active": true,
+ "avatar": undefined,
+ "name": "John Doe",
+ },
+ },
+ ]
+ }
+ />,
+ "key": "review",
+ "label": <React.Fragment>
+ <span>
+ hotspots.tabs.review_history
+ </span>
+ <span
+ className="counter-badge spacer-left"
+ >
+ 1
+ </span>
+ </React.Fragment>,
},
]
}
/>
<div
- className="boxed-group markdown big-padded"
- dangerouslySetInnerHTML={
- Object {
- "__html": "<p>This a <strong>strong</strong> message about vulnerability !</p>",
+ className="boxed-group big-padded"
+ >
+ <div
+ className="markdown"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "<p>This a <strong>strong</strong> message about vulnerability !</p>",
+ }
}
- }
- />
+ />
+ </div>
</Fragment>
`;
tabs={
Array [
Object {
+ "content": "<p>This a <strong>strong</strong> message about risk !</p>",
"key": "risk",
- "label": "hotspot.tabs.risk_description",
+ "label": "hotspots.tabs.risk_description",
},
Object {
+ "content": "<p>This a <strong>strong</strong> message about vulnerability !</p>",
"key": "vulnerability",
- "label": "hotspot.tabs.vulnerability_description",
+ "label": "hotspots.tabs.vulnerability_description",
},
Object {
+ "content": "<p>This a <strong>strong</strong> message about fixing !</p>",
"key": "fix",
- "label": "hotspot.tabs.fix_recommendations",
+ "label": "hotspots.tabs.fix_recommendations",
+ },
+ Object {
+ "content": <HotspotViewerReviewHistoryTab
+ history={
+ Array [
+ Object {
+ "date": "2013-05-13T17:55:41+0200",
+ "type": 0,
+ "user": Object {
+ "active": true,
+ "avatar": undefined,
+ "name": "John Doe",
+ },
+ },
+ ]
+ }
+ />,
+ "key": "review",
+ "label": <React.Fragment>
+ <span>
+ hotspots.tabs.review_history
+ </span>
+ <span
+ className="counter-badge spacer-left"
+ >
+ 1
+ </span>
+ </React.Fragment>,
},
]
}
/>
<div
- className="boxed-group markdown big-padded"
- dangerouslySetInnerHTML={
- Object {
- "__html": "<p>This a <strong>strong</strong> message about fixing !</p>",
+ className="boxed-group big-padded"
+ >
+ <div
+ className="markdown"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "<p>This a <strong>strong</strong> message about fixing !</p>",
+ }
}
- }
- />
+ />
+ </div>
</Fragment>
`;
exports[`should render correctly: no tabs 1`] = `""`;
-exports[`should render correctly: risk 1`] = `
+exports[`should render correctly: review 1`] = `
<Fragment>
<BoxedTabs
onSelect={[Function]}
- selected="risk"
+ selected="review"
tabs={
Array [
Object {
+ "content": "<p>This a <strong>strong</strong> message about risk !</p>",
"key": "risk",
- "label": "hotspot.tabs.risk_description",
+ "label": "hotspots.tabs.risk_description",
},
Object {
+ "content": "<p>This a <strong>strong</strong> message about vulnerability !</p>",
"key": "vulnerability",
- "label": "hotspot.tabs.vulnerability_description",
+ "label": "hotspots.tabs.vulnerability_description",
},
Object {
+ "content": "<p>This a <strong>strong</strong> message about fixing !</p>",
"key": "fix",
- "label": "hotspot.tabs.fix_recommendations",
+ "label": "hotspots.tabs.fix_recommendations",
+ },
+ Object {
+ "content": <HotspotViewerReviewHistoryTab
+ history={
+ Array [
+ Object {
+ "date": "2013-05-13T17:55:41+0200",
+ "type": 0,
+ "user": Object {
+ "active": true,
+ "avatar": undefined,
+ "name": "John Doe",
+ },
+ },
+ ]
+ }
+ />,
+ "key": "review",
+ "label": <React.Fragment>
+ <span>
+ hotspots.tabs.review_history
+ </span>
+ <span
+ className="counter-badge spacer-left"
+ >
+ 1
+ </span>
+ </React.Fragment>,
},
]
}
/>
<div
- className="boxed-group markdown big-padded"
- dangerouslySetInnerHTML={
- Object {
- "__html": "<p>This a <strong>strong</strong> message about risk !</p>",
+ className="boxed-group big-padded"
+ >
+ <HotspotViewerReviewHistoryTab
+ history={
+ Array [
+ Object {
+ "date": "2013-05-13T17:55:41+0200",
+ "type": 0,
+ "user": Object {
+ "active": true,
+ "avatar": undefined,
+ "name": "John Doe",
+ },
+ },
+ ]
}
+ />
+ </div>
+</Fragment>
+`;
+
+exports[`should render correctly: risk 1`] = `
+<Fragment>
+ <BoxedTabs
+ onSelect={[Function]}
+ selected="risk"
+ 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,
+ "avatar": undefined,
+ "name": "John Doe",
+ },
+ },
+ ]
+ }
+ />,
+ "key": "review",
+ "label": <React.Fragment>
+ <span>
+ hotspots.tabs.review_history
+ </span>
+ <span
+ className="counter-badge spacer-left"
+ >
+ 1
+ </span>
+ </React.Fragment>,
+ },
+ ]
}
/>
+ <div
+ className="boxed-group big-padded"
+ >
+ <div
+ className="markdown"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "<p>This a <strong>strong</strong> message about risk !</p>",
+ }
+ }
+ />
+ </div>
</Fragment>
`;
tabs={
Array [
Object {
+ "content": "<p>This a <strong>strong</strong> message about risk !</p>",
"key": "risk",
- "label": "hotspot.tabs.risk_description",
+ "label": "hotspots.tabs.risk_description",
},
Object {
+ "content": "<p>This a <strong>strong</strong> message about vulnerability !</p>",
"key": "vulnerability",
- "label": "hotspot.tabs.vulnerability_description",
+ "label": "hotspots.tabs.vulnerability_description",
},
Object {
+ "content": "<p>This a <strong>strong</strong> message about fixing !</p>",
"key": "fix",
- "label": "hotspot.tabs.fix_recommendations",
+ "label": "hotspots.tabs.fix_recommendations",
+ },
+ Object {
+ "content": <HotspotViewerReviewHistoryTab
+ history={
+ Array [
+ Object {
+ "date": "2013-05-13T17:55:41+0200",
+ "type": 0,
+ "user": Object {
+ "active": true,
+ "avatar": undefined,
+ "name": "John Doe",
+ },
+ },
+ ]
+ }
+ />,
+ "key": "review",
+ "label": <React.Fragment>
+ <span>
+ hotspots.tabs.review_history
+ </span>
+ <span
+ className="counter-badge spacer-left"
+ >
+ 1
+ </span>
+ </React.Fragment>,
},
]
}
/>
<div
- className="boxed-group markdown big-padded"
- dangerouslySetInnerHTML={
- Object {
- "__html": "<p>This a <strong>strong</strong> message about vulnerability !</p>",
+ className="boxed-group big-padded"
+ >
+ <div
+ className="markdown"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "<p>This a <strong>strong</strong> message about vulnerability !</p>",
+ }
}
- }
- />
+ />
+ </div>
</Fragment>
`;
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { groupBy, sortBy } from 'lodash';
-import { DetailedHotspot, RawHotspot, RiskExposure } from '../../types/security-hotspots';
+import {
+ Hotspot,
+ RawHotspot,
+ ReviewHistoryElement,
+ ReviewHistoryType,
+ RiskExposure
+} from '../../types/security-hotspots';
export const RISK_EXPOSURE_LEVELS = [RiskExposure.HIGH, RiskExposure.MEDIUM, RiskExposure.LOW];
}
export function constructSourceViewerFile(
- { component, project }: DetailedHotspot,
+ { component, project }: Hotspot,
lines?: number
): T.SourceViewerFile {
return {
uuid: ''
};
}
+
+export function getHotspotReviewHistory(hotspot: Hotspot): ReviewHistoryElement[] {
+ const history: ReviewHistoryElement[] = [];
+
+ if (hotspot.creationDate) {
+ history.push({
+ type: ReviewHistoryType.Creation,
+ date: hotspot.creationDate,
+ user: {
+ avatar: hotspot.author.avatar,
+ name: hotspot.author.name || hotspot.author.login,
+ active: hotspot.author.active
+ }
+ });
+ }
+
+ if (hotspot.changelog) {
+ history.push(
+ ...hotspot.changelog.map(log => ({
+ type: ReviewHistoryType.Diff,
+ date: log.creationDate,
+ user: {
+ avatar: log.avatar,
+ name: log.userName || log.user,
+ active: log.isUserActive
+ },
+ diffs: log.diffs
+ }))
+ );
+ }
+
+ return sortBy(history, elt => elt.date);
+}
*/
import { ComponentQualifier } from '../../types/component';
import {
- DetailedHotspot,
- DetailedHotspotRule,
+ Hotspot,
HotspotResolution,
+ HotspotRule,
HotspotStatus,
RawHotspot,
+ ReviewHistoryElement,
+ ReviewHistoryType,
RiskExposure
} from '../../types/security-hotspots';
import { mockComponent, mockUser } from '../testMocks';
};
}
-export function mockDetailledHotspot(overrides?: Partial<DetailedHotspot>): DetailedHotspot {
+export function mockHotspot(overrides?: Partial<Hotspot>): Hotspot {
return {
assignee: mockUser(),
author: mockUser(),
+ changelog: [],
component: mockComponent({ qualifier: ComponentQualifier.File }),
creationDate: '2013-05-13T17:55:41+0200',
key: '01fc972e-2a3c-433e-bcae-0bd7f88f5123',
message: "'3' is a magic number.",
project: mockComponent({ qualifier: ComponentQualifier.Project }),
resolution: HotspotResolution.FIXED,
- rule: mockDetailledHotspotRule(),
+ rule: mockHotspotRule(),
status: HotspotStatus.REVIEWED,
textRange: {
startLine: 142,
};
}
-export function mockDetailledHotspotRule(
- overrides?: Partial<DetailedHotspotRule>
-): DetailedHotspotRule {
+export function mockHotspotRule(overrides?: Partial<HotspotRule>): HotspotRule {
return {
key: 'squid:S2077',
name: 'That rule',
...overrides
};
}
+
+export function mockHotspotReviewHistoryElement(
+ overrides?: Partial<ReviewHistoryElement>
+): ReviewHistoryElement {
+ return {
+ date: '2019-09-13T17:55:42+0200',
+ type: ReviewHistoryType.Creation,
+ user: mockUser(),
+ ...overrides
+ };
+}
vulnerabilityProbability: RiskExposure;
}
-export interface DetailedHotspot {
+export interface Hotspot {
assignee?: Pick<T.UserBase, 'active' | 'login' | 'name'>;
- author?: Pick<T.UserBase, 'login'>;
+ author: Pick<T.UserBase, 'active' | 'avatar' | 'login' | 'name'>;
+ changelog?: T.IssueChangelog[];
component: T.Component;
creationDate: string;
key: string;
message: string;
project: T.Component;
resolution?: string;
- rule: DetailedHotspotRule;
+ rule: HotspotRule;
status: string;
textRange: T.TextRange;
updateDate: string;
key: string;
}
-export interface DetailedHotspotRule {
+export interface HotspotRule {
fixRecommendations?: string;
key: string;
name: string;
vulnerabilityProbability: RiskExposure;
}
+export interface ReviewHistoryElement {
+ type: ReviewHistoryType;
+ date: string;
+ user: Pick<T.UserBase, 'active' | 'avatar' | 'name'>;
+ diffs?: T.IssueChangelogDiff[];
+}
+
+export enum ReviewHistoryType {
+ Creation,
+ Diff
+}
+
export interface HotspotSearchResponse {
components?: { key: string; qualifier: string; name: string }[];
hotspots: RawHotspot[];
hotspot.category=Category:
hotspot.status=Status:
hotspot.assigned_to=Assigned to:
-hotspot.tabs.risk_description=What's the risk?
-hotspot.tabs.vulnerability_description=Are you vulnerable?
-hotspot.tabs.fix_recommendations=How can you fix it?
+hotspots.tabs.risk_description=What's the risk?
+hotspots.tabs.vulnerability_description=Are you vulnerable?
+hotspots.tabs.fix_recommendations=How can you fix it?
+hotspots.tabs.review_history=Review history
+hotspots.tabs.review_history.created=created Security Hotspot
+
hotspot.change_status.REVIEWED=Change status
hotspot.change_status.TO_REVIEW=Review Hotspot
hotspot.filters.status.to_review=To review
hotspot.filters.status.fixed=Reviewed as fixed
hotspot.filters.status.safe=Reviewed as safe
+hotspots.review_hotspot=Review Hotspot
hotspots.form.title=Mark Security Hotspot as: