--- /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 { getJSON } from 'sonar-ui-common/helpers/request';
+import throwGlobalError from '../app/utils/throwGlobalError';
+import { DetailedHotspot, HotspotSearchResponse } from '../types/security-hotspots';
+
+export function getSecurityHotspots(data: {
+ projectKey: string;
+ p: number;
+ ps: number;
+}): Promise<HotspotSearchResponse> {
+ return getJSON('/api/hotspots/search', data).catch(throwGlobalError);
+}
+
+export function getSecurityHotspotDetails(securityHotspotKey: string): Promise<DetailedHotspot> {
+ return getJSON('/api/hotspots/show', { hotspot: securityHotspotKey }).catch(throwGlobalError);
+}
+++ /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 { getJSON } from 'sonar-ui-common/helpers/request';
-import throwGlobalError from '../app/utils/throwGlobalError';
-import { HotspotSearchResponse } from '../types/securityHotspots';
-
-export function getSecurityHotspots(data: {
- projectKey: string;
- p: number;
- ps: number;
-}): Promise<HotspotSearchResponse> {
- return getJSON('/api/hotspots/search', data).catch(throwGlobalError);
-}
margin-top: 4px !important;
}
+.big-padded {
+ padding: calc(2 * var(--gridSize));
+}
+
td.little-spacer-left {
padding-left: 4px !important;
}
*/
import * as React from 'react';
import { addNoFooterPageClass, removeNoFooterPageClass } from 'sonar-ui-common/helpers/pages';
-import { getSecurityHotspots } from '../../api/securityHotspots';
+import { getSecurityHotspots } from '../../api/security-hotspots';
import { getStandards } from '../../helpers/security-standard';
import { BranchLike } from '../../types/branch-like';
-import { RawHotspot } from '../../types/securityHotspots';
+import { RawHotspot } from '../../types/security-hotspots';
import SecurityHotspotsAppRenderer from './SecurityHotspotsAppRenderer';
import './styles.css';
import { sortHotspots } from './utils';
interface State {
hotspots: RawHotspot[];
loading: boolean;
- securityCategories: T.Dict<{ title: string; description?: string }>;
+ securityCategories: T.StandardSecurityCategories;
selectedHotspotKey: string | undefined;
}
import A11ySkipTarget from '../../app/components/a11y/A11ySkipTarget';
import Suggestions from '../../app/components/embed-docs-modal/Suggestions';
import ScreenPositionHelper from '../../components/common/ScreenPositionHelper';
-import { RawHotspot } from '../../types/securityHotspots';
+import { RawHotspot } from '../../types/security-hotspots';
import FilterBar from './components/FilterBar';
import HotspotList from './components/HotspotList';
import HotspotViewer from './components/HotspotViewer';
loading: boolean;
onHotspotClick: (key: string) => void;
selectedHotspotKey?: string;
- securityCategories: T.Dict<{ title: string; description?: string }>;
+ securityCategories: T.StandardSecurityCategories;
}
export default function SecurityHotspotsAppRenderer(props: SecurityHotspotsAppRendererProps) {
/>
</div>
<div className="main">
- <HotspotViewer />
+ {selectedHotspotKey && (
+ <HotspotViewer
+ hotspotKey={selectedHotspotKey}
+ securityCategories={securityCategories}
+ />
+ )}
</div>
</div>
)}
import * as React from 'react';
import { addNoFooterPageClass } from 'sonar-ui-common/helpers/pages';
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
-import { getSecurityHotspots } from '../../../api/securityHotspots';
+import { getSecurityHotspots } from '../../../api/security-hotspots';
import { mockMainBranch } from '../../../helpers/mocks/branch-like';
-import { mockHotspot } from '../../../helpers/mocks/security-hotspots';
+import { mockRawHotspot } from '../../../helpers/mocks/security-hotspots';
import { getStandards } from '../../../helpers/security-standard';
import { mockComponent } from '../../../helpers/testMocks';
import SecurityHotspotsApp from '../SecurityHotspotsApp';
removeNoFooterPageClass: jest.fn()
}));
-jest.mock('../../../api/securityHotspots', () => ({
+jest.mock('../../../api/security-hotspots', () => ({
getSecurityHotspots: jest.fn().mockResolvedValue({ hotspots: [], rules: [] })
}));
const sonarsourceSecurity = { cat1: { title: 'cat 1' } };
(getStandards as jest.Mock).mockResolvedValue({ sonarsourceSecurity });
- const hotspots = [mockHotspot()];
+ const hotspots = [mockRawHotspot()];
(getSecurityHotspots as jest.Mock).mockResolvedValue({
hotspots
});
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { mockHotspot } from '../../../helpers/mocks/security-hotspots';
+import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
+import { mockRawHotspot } from '../../../helpers/mocks/security-hotspots';
import SecurityHotspotsAppRenderer, {
SecurityHotspotsAppRendererProps
} from '../SecurityHotspotsAppRenderer';
it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
+ expect(
+ shallowRender()
+ .find(ScreenPositionHelper)
+ .dive()
+ ).toMatchSnapshot();
});
it('should render correctly with hotspots', () => {
- const hotspots = [mockHotspot({ key: 'h1' }), mockHotspot({ key: 'h2' })];
- expect(shallowRender({ hotspots })).toMatchSnapshot();
- expect(shallowRender({ hotspots, selectedHotspotKey: 'h2' })).toMatchSnapshot();
+ const hotspots = [mockRawHotspot({ key: 'h1' }), mockRawHotspot({ key: 'h2' })];
+ expect(
+ shallowRender({ hotspots })
+ .find(ScreenPositionHelper)
+ .dive()
+ ).toMatchSnapshot();
+ expect(
+ shallowRender({ hotspots, selectedHotspotKey: 'h2' })
+ .find(ScreenPositionHelper)
+ .dive()
+ ).toMatchSnapshot();
});
function shallowRender(props: Partial<SecurityHotspotsAppRendererProps> = {}) {
</div>
`;
+exports[`should render correctly 2`] = `
+<div>
+ <div
+ className="wrapper"
+ style={
+ Object {
+ "top": 0,
+ }
+ }
+ >
+ <Suggestions
+ suggestions="security_hotspots"
+ />
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="hotspots.page"
+ />
+ <A11ySkipTarget
+ anchor="security_hotspots_main"
+ />
+ <DeferredSpinner
+ className="huge-spacer-left big-spacer-top"
+ loading={false}
+ timeout={100}
+ >
+ <div
+ className="display-flex-column display-flex-center"
+ >
+ <img
+ alt="hotspots.page"
+ className="huge-spacer-top"
+ height={166}
+ src="/images/hotspot-large.svg"
+ />
+ <h1
+ className="huge-spacer-top"
+ >
+ hotspots.no_hotspots.title
+ </h1>
+ <div
+ className="abs-width-400 text-center big-spacer-top"
+ >
+ hotspots.no_hotspots.description
+ </div>
+ <Link
+ className="big-spacer-top"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ target="_blank"
+ to={
+ Object {
+ "pathname": "/documentation/user-guide/security-hotspots/",
+ }
+ }
+ >
+ hotspots.learn_more
+ </Link>
+ </div>
+ </DeferredSpinner>
+ </div>
+</div>
+`;
+
exports[`should render correctly with hotspots 1`] = `
-<div
- id="security_hotspots"
->
- <FilterBar />
- <ScreenPositionHelper>
- <Component />
- </ScreenPositionHelper>
+<div>
+ <div
+ className="wrapper"
+ style={
+ Object {
+ "top": 0,
+ }
+ }
+ >
+ <Suggestions
+ suggestions="security_hotspots"
+ />
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="hotspots.page"
+ />
+ <A11ySkipTarget
+ anchor="security_hotspots_main"
+ />
+ <DeferredSpinner
+ className="huge-spacer-left big-spacer-top"
+ loading={false}
+ timeout={100}
+ >
+ <div
+ className="layout-page"
+ >
+ <div
+ className="sidebar"
+ >
+ <HotspotList
+ hotspots={
+ Array [
+ Object {
+ "author": "Developer 1",
+ "component": "com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest",
+ "creationDate": "2013-05-13T17:55:39+0200",
+ "key": "h1",
+ "line": 81,
+ "message": "'3' is a magic number.",
+ "project": "com.github.kevinsawicki:http-request",
+ "resolution": "FALSE-POSITIVE",
+ "rule": "checkstyle:com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck",
+ "securityCategory": "command-injection",
+ "status": "RESOLVED",
+ "updateDate": "2013-05-13T17:55:39+0200",
+ "vulnerabilityProbability": "HIGH",
+ },
+ Object {
+ "author": "Developer 1",
+ "component": "com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest",
+ "creationDate": "2013-05-13T17:55:39+0200",
+ "key": "h2",
+ "line": 81,
+ "message": "'3' is a magic number.",
+ "project": "com.github.kevinsawicki:http-request",
+ "resolution": "FALSE-POSITIVE",
+ "rule": "checkstyle:com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck",
+ "securityCategory": "command-injection",
+ "status": "RESOLVED",
+ "updateDate": "2013-05-13T17:55:39+0200",
+ "vulnerabilityProbability": "HIGH",
+ },
+ ]
+ }
+ onHotspotClick={[MockFunction]}
+ securityCategories={Object {}}
+ />
+ </div>
+ <div
+ className="main"
+ />
+ </div>
+ </DeferredSpinner>
+ </div>
</div>
`;
exports[`should render correctly with hotspots 2`] = `
-<div
- id="security_hotspots"
->
- <FilterBar />
- <ScreenPositionHelper>
- <Component />
- </ScreenPositionHelper>
+<div>
+ <div
+ className="wrapper"
+ style={
+ Object {
+ "top": 0,
+ }
+ }
+ >
+ <Suggestions
+ suggestions="security_hotspots"
+ />
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="hotspots.page"
+ />
+ <A11ySkipTarget
+ anchor="security_hotspots_main"
+ />
+ <DeferredSpinner
+ className="huge-spacer-left big-spacer-top"
+ loading={false}
+ timeout={100}
+ >
+ <div
+ className="layout-page"
+ >
+ <div
+ className="sidebar"
+ >
+ <HotspotList
+ hotspots={
+ Array [
+ Object {
+ "author": "Developer 1",
+ "component": "com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest",
+ "creationDate": "2013-05-13T17:55:39+0200",
+ "key": "h1",
+ "line": 81,
+ "message": "'3' is a magic number.",
+ "project": "com.github.kevinsawicki:http-request",
+ "resolution": "FALSE-POSITIVE",
+ "rule": "checkstyle:com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck",
+ "securityCategory": "command-injection",
+ "status": "RESOLVED",
+ "updateDate": "2013-05-13T17:55:39+0200",
+ "vulnerabilityProbability": "HIGH",
+ },
+ Object {
+ "author": "Developer 1",
+ "component": "com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest",
+ "creationDate": "2013-05-13T17:55:39+0200",
+ "key": "h2",
+ "line": 81,
+ "message": "'3' is a magic number.",
+ "project": "com.github.kevinsawicki:http-request",
+ "resolution": "FALSE-POSITIVE",
+ "rule": "checkstyle:com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck",
+ "securityCategory": "command-injection",
+ "status": "RESOLVED",
+ "updateDate": "2013-05-13T17:55:39+0200",
+ "vulnerabilityProbability": "HIGH",
+ },
+ ]
+ }
+ onHotspotClick={[MockFunction]}
+ securityCategories={Object {}}
+ selectedHotspotKey="h2"
+ />
+ </div>
+ <div
+ className="main"
+ >
+ <HotspotViewer
+ hotspotKey="h2"
+ securityCategories={Object {}}
+ />
+ </div>
+ </div>
+ </DeferredSpinner>
+ </div>
</div>
`;
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { mockHotspot } from '../../../helpers/mocks/security-hotspots';
-import { RiskExposure } from '../../../types/securityHotspots';
+import { mockRawHotspot } from '../../../helpers/mocks/security-hotspots';
+import { RiskExposure } from '../../../types/security-hotspots';
import { groupByCategory, mapRules, sortHotspots } from '../utils';
const hotspots = [
- mockHotspot({
+ mockRawHotspot({
key: '3',
vulnerabilityProbability: RiskExposure.HIGH,
securityCategory: 'object-injection',
message: 'tfdh'
}),
- mockHotspot({
+ mockRawHotspot({
key: '5',
vulnerabilityProbability: RiskExposure.MEDIUM,
securityCategory: 'xpath-injection',
message: 'asdf'
}),
- mockHotspot({
+ mockRawHotspot({
key: '1',
vulnerabilityProbability: RiskExposure.HIGH,
securityCategory: 'dos',
message: 'a'
}),
- mockHotspot({
+ mockRawHotspot({
key: '7',
vulnerabilityProbability: RiskExposure.LOW,
securityCategory: 'ssrf',
message: 'rrrr'
}),
- mockHotspot({
+ mockRawHotspot({
key: '2',
vulnerabilityProbability: RiskExposure.HIGH,
securityCategory: 'dos',
message: 'b'
}),
- mockHotspot({
+ mockRawHotspot({
key: '8',
vulnerabilityProbability: RiskExposure.LOW,
securityCategory: 'ssrf',
message: 'sssss'
}),
- mockHotspot({
+ mockRawHotspot({
key: '4',
vulnerabilityProbability: RiskExposure.MEDIUM,
securityCategory: 'log-injection',
message: 'asdf'
}),
- mockHotspot({
+ mockRawHotspot({
key: '9',
vulnerabilityProbability: RiskExposure.LOW,
securityCategory: 'xxe',
message: 'aaa'
}),
- mockHotspot({
+ mockRawHotspot({
key: '6',
vulnerabilityProbability: RiskExposure.LOW,
securityCategory: 'xss',
import * as React from 'react';
import ChevronDownIcon from 'sonar-ui-common/components/icons/ChevronDownIcon';
import ChevronUpIcon from 'sonar-ui-common/components/icons/ChevronUpIcon';
-import { RawHotspot } from '../../../types/securityHotspots';
+import { RawHotspot } from '../../../types/security-hotspots';
import HotspotListItem from './HotspotListItem';
export interface HotspotCategoryProps {
import * as React from 'react';
import SecurityHotspotIcon from 'sonar-ui-common/components/icons/SecurityHotspotIcon';
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
-import { RawHotspot, RiskExposure } from '../../../types/securityHotspots';
+import { RawHotspot, RiskExposure } from '../../../types/security-hotspots';
import { groupByCategory, RISK_EXPOSURE_LEVELS } from '../utils';
import HotspotCategory from './HotspotCategory';
import './HotspotList.css';
export interface HotspotListProps {
hotspots: RawHotspot[];
onHotspotClick: (key: string) => void;
- securityCategories: T.Dict<{ title: string; description?: string }>;
+ securityCategories: T.StandardSecurityCategories;
selectedHotspotKey: string | undefined;
}
import * as classNames from 'classnames';
import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import { RawHotspot } from '../../../types/securityHotspots';
+import { RawHotspot } from '../../../types/security-hotspots';
export interface HotspotListItemProps {
hotspot: RawHotspot;
* 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 { getSecurityHotspotDetails } from '../../../api/security-hotspots';
+import { DetailedHotspot } from '../../../types/security-hotspots';
+import HotspotViewerRenderer from './HotspotViewerRenderer';
+
+interface Props {
+ hotspotKey: string;
+ securityCategories: T.StandardSecurityCategories;
+}
+
+interface State {
+ hotspot?: DetailedHotspot;
+ loading: boolean;
+}
+
+export default class HotspotViewer extends React.PureComponent<Props, State> {
+ mounted = false;
+
+ componentWillMount() {
+ this.mounted = true;
+ this.fetchHotspot();
+ }
+
+ componentDidUpdate(prevProps: Props) {
+ if (prevProps.hotspotKey !== this.props.hotspotKey) {
+ this.fetchHotspot();
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchHotspot() {
+ this.setState({ loading: true });
+ return getSecurityHotspotDetails(this.props.hotspotKey)
+ .then(hotspot => this.mounted && this.setState({ hotspot }))
+ .finally(() => this.mounted && this.setState({ loading: false }));
+ }
-export interface Props {}
+ render() {
+ const { securityCategories } = this.props;
+ const { hotspot, loading } = this.state;
-export default function HotspotViewer(props: Props) {
- return (
- <div {...props} className="hotspot-viewer">
- Show hotspot details
- </div>
- );
+ return (
+ <HotspotViewerRenderer
+ hotspot={hotspot}
+ loading={loading}
+ securityCategories={securityCategories}
+ />
+ );
+ }
}
--- /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 DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
+import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
+import { DetailedHotspot } from '../../../types/security-hotspots';
+import HotspotViewerTabs from './HotspotViewerTabs';
+
+export interface HotspotViewerRendererProps {
+ hotspot?: DetailedHotspot;
+ loading: boolean;
+ securityCategories: T.StandardSecurityCategories;
+}
+
+export default function HotspotViewerRenderer(props: HotspotViewerRendererProps) {
+ const { hotspot, loading, securityCategories } = props;
+
+ return (
+ <DeferredSpinner loading={loading}>
+ {hotspot && (
+ <div className="big-padded">
+ <div className="big-spacer-bottom">
+ <h1>{hotspot.message}</h1>
+ <div className="text-muted">
+ <span>{translate('hotspot.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 className="badge little-spacer-left">
+ {translate('issue.status', hotspot.status)}
+ </span>
+ {hotspot.assignee && hotspot.assignee.name && (
+ <>
+ <span className="huge-spacer-left">{translate('hotspot.assigned_to')}</span>
+ <strong className="little-spacer-left">
+ {hotspot.assignee.active
+ ? hotspot.assignee.name
+ : translateWithParameters('user.x_deleted', hotspot.assignee.name)}
+ </strong>
+ </>
+ )}
+ </div>
+ <HotspotViewerTabs hotspot={hotspot} />
+ </div>
+ )}
+ </DeferredSpinner>
+ );
+}
--- /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 { sanitize } from 'dompurify';
+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';
+
+export interface HotspotViewerTabsProps {
+ hotspot: DetailedHotspot;
+}
+
+export enum Tabs {
+ RiskDescription = 'risk',
+ VulnerabilityDescription = 'vulnerability',
+ FixRecommendation = 'fix'
+}
+
+export default function HotspotViewerTabs(props: HotspotViewerTabsProps) {
+ const { hotspot } = props;
+ const [currentTab, setCurrentTab] = React.useState(Tabs.RiskDescription);
+
+ const tabs = {
+ [Tabs.RiskDescription]: {
+ title: translate('hotspot.tabs.risk_description'),
+ content: hotspot.rule.riskDescription || ''
+ },
+ [Tabs.VulnerabilityDescription]: {
+ title: translate('hotspot.tabs.vulnerability_description'),
+ content: hotspot.rule.vulnerabilityDescription || ''
+ },
+ [Tabs.FixRecommendation]: {
+ title: translate('hotspot.tabs.fix_recommendations'),
+ content: hotspot.rule.fixRecommendations || ''
+ }
+ };
+
+ const tabsToDisplay = Object.values(Tabs)
+ .filter(tab => Boolean(tabs[tab].content))
+ .map(tab => ({ key: tab, label: tabs[tab].title }));
+
+ if (tabsToDisplay.length === 0) {
+ return null;
+ }
+
+ if (!tabsToDisplay.find(tab => tab.key === currentTab)) {
+ setCurrentTab(tabsToDisplay[0].key);
+ }
+
+ return (
+ <>
+ <BoxedTabs onSelect={tab => setCurrentTab(tab)} selected={currentTab} tabs={tabsToDisplay} />
+ <div
+ className="boxed-group markdown big-padded"
+ dangerouslySetInnerHTML={{ __html: sanitize(tabs[currentTab].content) }}
+ />
+ </>
+ );
+}
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { mockHotspot } from '../../../../helpers/mocks/security-hotspots';
+import { mockRawHotspot } from '../../../../helpers/mocks/security-hotspots';
import HotspotCategory, { HotspotCategoryProps } from '../HotspotCategory';
it('should render correctly', () => {
});
it('should render correctly with hotspots', () => {
- const hotspots = [mockHotspot({ key: 'h1' }), mockHotspot({ key: 'h2' })];
+ const hotspots = [mockRawHotspot({ key: 'h1' }), mockRawHotspot({ key: 'h2' })];
expect(shallowRender({ hotspots })).toMatchSnapshot();
});
it('should handle collapse and expand', () => {
- const wrapper = shallowRender({ hotspots: [mockHotspot()] });
+ const wrapper = shallowRender({ hotspots: [mockRawHotspot()] });
wrapper.find('.hotspot-category-header').simulate('click');
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { mockHotspot } from '../../../../helpers/mocks/security-hotspots';
-import { RiskExposure } from '../../../../types/securityHotspots';
+import { mockRawHotspot } from '../../../../helpers/mocks/security-hotspots';
+import { RiskExposure } from '../../../../types/security-hotspots';
import HotspotList, { HotspotListProps } from '../HotspotList';
it('should render correctly', () => {
it('should render correctly with hotspots', () => {
const hotspots = [
- mockHotspot({ key: 'h1', securityCategory: 'cat2' }),
- mockHotspot({ key: 'h2', securityCategory: 'cat1' }),
- mockHotspot({
+ mockRawHotspot({ key: 'h1', securityCategory: 'cat2' }),
+ mockRawHotspot({ key: 'h2', securityCategory: 'cat1' }),
+ mockRawHotspot({
key: 'h3',
securityCategory: 'cat1',
vulnerabilityProbability: RiskExposure.MEDIUM
}),
- mockHotspot({
+ mockRawHotspot({
key: 'h4',
securityCategory: 'cat1',
vulnerabilityProbability: RiskExposure.MEDIUM
}),
- mockHotspot({
+ mockRawHotspot({
key: 'h5',
securityCategory: 'cat2',
vulnerabilityProbability: RiskExposure.MEDIUM
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { mockHotspot } from '../../../../helpers/mocks/security-hotspots';
+import { mockRawHotspot } from '../../../../helpers/mocks/security-hotspots';
import { HotspotListItem, HotspotListItemProps } from '../HotspotListItem';
it('should render correctly', () => {
});
it('should handle click', () => {
- const hotspot = mockHotspot({ key: 'hotspotKey' });
+ const hotspot = mockRawHotspot({ key: 'hotspotKey' });
const onClick = jest.fn();
const wrapper = shallowRender({ hotspot, onClick });
function shallowRender(props: Partial<HotspotListItemProps> = {}) {
return shallow(
- <HotspotListItem hotspot={mockHotspot()} onClick={jest.fn()} selected={false} {...props} />
+ <HotspotListItem hotspot={mockRawHotspot()} onClick={jest.fn()} selected={false} {...props} />
);
}
--- /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 { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
+import { getSecurityHotspotDetails } from '../../../../api/security-hotspots';
+import HotspotViewer from '../HotspotViewer';
+
+const hotspotKey = 'hotspot-key';
+
+jest.mock('../../../../api/security-hotspots', () => ({
+ getSecurityHotspotDetails: jest.fn().mockResolvedValue({ id: `I am a detailled hotspot` })
+}));
+
+it('should render correctly', async () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+
+ await waitAndUpdate(wrapper);
+
+ expect(wrapper).toMatchSnapshot();
+ expect(getSecurityHotspotDetails).toHaveBeenCalledWith(hotspotKey);
+
+ const newHotspotKey = `new-${hotspotKey}`;
+ wrapper.setProps({ hotspotKey: newHotspotKey });
+
+ await waitAndUpdate(wrapper);
+ expect(getSecurityHotspotDetails).toHaveBeenCalledWith(newHotspotKey);
+});
+
+function shallowRender(props?: Partial<HotspotViewer['props']>) {
+ return shallow<HotspotViewer>(
+ <HotspotViewer
+ hotspotKey={hotspotKey}
+ securityCategories={{ cat1: { title: 'cat1' } }}
+ {...props}
+ />
+ );
+}
--- /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 { mockDetailledHotspot } from '../../../../helpers/mocks/security-hotspots';
+import { mockUser } from '../../../../helpers/testMocks';
+import HotspotViewerRenderer, { HotspotViewerRendererProps } from '../HotspotViewerRenderer';
+
+it('should render correctly', () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+ expect(shallowRender({ hotspot: undefined })).toMatchSnapshot('no hotspot');
+ expect(
+ shallowRender({ hotspot: mockDetailledHotspot({ assignee: mockUser({ active: false }) }) })
+ ).toMatchSnapshot('deleted assignee');
+});
+
+function shallowRender(props?: Partial<HotspotViewerRendererProps>) {
+ return shallow(
+ <HotspotViewerRenderer
+ hotspot={mockDetailledHotspot()}
+ loading={false}
+ securityCategories={{ 'sql-injection': { title: 'SQL injection' } }}
+ {...props}
+ />
+ );
+}
--- /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 BoxedTabs from 'sonar-ui-common/components/controls/BoxedTabs';
+import {
+ mockDetailledHotspot,
+ mockDetailledHotspotRule
+} from '../../../../helpers/mocks/security-hotspots';
+import HotspotViewerTabs, { HotspotViewerTabsProps, Tabs } from '../HotspotViewerTabs';
+
+it('should render correctly', () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot('risk');
+
+ const onSelect = wrapper.find(BoxedTabs).prop('onSelect') as (tab: Tabs) => void;
+
+ if (!onSelect) {
+ fail('onSelect should be defined');
+ } else {
+ onSelect(Tabs.VulnerabilityDescription);
+ expect(wrapper).toMatchSnapshot('vulnerability');
+
+ onSelect(Tabs.FixRecommendation);
+ expect(wrapper).toMatchSnapshot('fix');
+ }
+
+ expect(
+ shallowRender({
+ hotspot: mockDetailledHotspot({
+ rule: mockDetailledHotspotRule({ riskDescription: undefined })
+ })
+ })
+ ).toMatchSnapshot('empty tab');
+
+ expect(
+ shallowRender({
+ hotspot: mockDetailledHotspot({
+ rule: mockDetailledHotspotRule({
+ riskDescription: undefined,
+ fixRecommendations: undefined,
+ vulnerabilityDescription: undefined
+ })
+ })
+ })
+ ).toMatchSnapshot('no tabs');
+});
+
+function shallowRender(props?: Partial<HotspotViewerTabsProps>) {
+ return shallow(<HotspotViewerTabs hotspot={mockDetailledHotspot()} {...props} />);
+}
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<HotspotViewerRenderer
+ loading={true}
+ securityCategories={
+ Object {
+ "cat1": Object {
+ "title": "cat1",
+ },
+ }
+ }
+/>
+`;
+
+exports[`should render correctly 2`] = `
+<HotspotViewerRenderer
+ hotspot={
+ Object {
+ "id": "I am a detailled hotspot",
+ }
+ }
+ loading={false}
+ securityCategories={
+ Object {
+ "cat1": Object {
+ "title": "cat1",
+ },
+ }
+ }
+/>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<DeferredSpinner
+ loading={false}
+ timeout={100}
+>
+ <div
+ className="big-padded"
+ >
+ <div
+ className="big-spacer-bottom"
+ >
+ <h1>
+ '3' is a magic number.
+ </h1>
+ <div
+ className="text-muted"
+ >
+ <span>
+ hotspot.category
+ </span>
+ <span
+ className="little-spacer-left"
+ >
+ SQL injection
+ </span>
+ </div>
+ </div>
+ <div
+ className="huge-spacer-bottom"
+ >
+ <span>
+ hotspot.status
+ </span>
+ <span
+ className="badge little-spacer-left"
+ >
+ issue.status.RESOLVED
+ </span>
+ <span
+ className="huge-spacer-left"
+ >
+ hotspot.assigned_to
+ </span>
+ <strong
+ className="little-spacer-left"
+ >
+ John Doe
+ </strong>
+ </div>
+ <HotspotViewerTabs
+ hotspot={
+ Object {
+ "assignee": Object {
+ "active": true,
+ "local": true,
+ "login": "john.doe",
+ "name": "John Doe",
+ },
+ "author": Object {
+ "active": true,
+ "local": true,
+ "login": "john.doe",
+ "name": "John Doe",
+ },
+ "component": Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "FIL",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ },
+ "creationDate": "2013-05-13T17:55:41+0200",
+ "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123",
+ "line": 142,
+ "message": "'3' is a magic number.",
+ "project": Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ },
+ "resolution": "FALSE-POSITIVE",
+ "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": "RESOLVED",
+ "textRange": Object {
+ "endLine": 142,
+ "endOffset": 83,
+ "startLine": 142,
+ "startOffset": 26,
+ },
+ "updateDate": "2013-05-13T17:55:42+0200",
+ }
+ }
+ />
+ </div>
+</DeferredSpinner>
+`;
+
+exports[`should render correctly: deleted assignee 1`] = `
+<DeferredSpinner
+ loading={false}
+ timeout={100}
+>
+ <div
+ className="big-padded"
+ >
+ <div
+ className="big-spacer-bottom"
+ >
+ <h1>
+ '3' is a magic number.
+ </h1>
+ <div
+ className="text-muted"
+ >
+ <span>
+ hotspot.category
+ </span>
+ <span
+ className="little-spacer-left"
+ >
+ SQL injection
+ </span>
+ </div>
+ </div>
+ <div
+ className="huge-spacer-bottom"
+ >
+ <span>
+ hotspot.status
+ </span>
+ <span
+ className="badge little-spacer-left"
+ >
+ issue.status.RESOLVED
+ </span>
+ <span
+ className="huge-spacer-left"
+ >
+ hotspot.assigned_to
+ </span>
+ <strong
+ className="little-spacer-left"
+ >
+ user.x_deleted.John Doe
+ </strong>
+ </div>
+ <HotspotViewerTabs
+ hotspot={
+ Object {
+ "assignee": Object {
+ "active": false,
+ "local": true,
+ "login": "john.doe",
+ "name": "John Doe",
+ },
+ "author": Object {
+ "active": true,
+ "local": true,
+ "login": "john.doe",
+ "name": "John Doe",
+ },
+ "component": Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "FIL",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ },
+ "creationDate": "2013-05-13T17:55:41+0200",
+ "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123",
+ "line": 142,
+ "message": "'3' is a magic number.",
+ "project": Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ },
+ "resolution": "FALSE-POSITIVE",
+ "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": "RESOLVED",
+ "textRange": Object {
+ "endLine": 142,
+ "endOffset": 83,
+ "startLine": 142,
+ "startOffset": 26,
+ },
+ "updateDate": "2013-05-13T17:55:42+0200",
+ }
+ }
+ />
+ </div>
+</DeferredSpinner>
+`;
+
+exports[`should render correctly: no hotspot 1`] = `
+<DeferredSpinner
+ loading={false}
+ timeout={100}
+/>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: empty tab 1`] = `
+<Fragment>
+ <BoxedTabs
+ onSelect={[Function]}
+ selected="vulnerability"
+ tabs={
+ Array [
+ Object {
+ "key": "vulnerability",
+ "label": "hotspot.tabs.vulnerability_description",
+ },
+ Object {
+ "key": "fix",
+ "label": "hotspot.tabs.fix_recommendations",
+ },
+ ]
+ }
+ />
+ <div
+ className="boxed-group markdown big-padded"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "<p>This a <strong>strong</strong> message about vulnerability !</p>",
+ }
+ }
+ />
+</Fragment>
+`;
+
+exports[`should render correctly: fix 1`] = `
+<Fragment>
+ <BoxedTabs
+ onSelect={[Function]}
+ selected="fix"
+ tabs={
+ Array [
+ Object {
+ "key": "risk",
+ "label": "hotspot.tabs.risk_description",
+ },
+ Object {
+ "key": "vulnerability",
+ "label": "hotspot.tabs.vulnerability_description",
+ },
+ Object {
+ "key": "fix",
+ "label": "hotspot.tabs.fix_recommendations",
+ },
+ ]
+ }
+ />
+ <div
+ className="boxed-group markdown big-padded"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "<p>This a <strong>strong</strong> message about fixing !</p>",
+ }
+ }
+ />
+</Fragment>
+`;
+
+exports[`should render correctly: no tabs 1`] = `""`;
+
+exports[`should render correctly: risk 1`] = `
+<Fragment>
+ <BoxedTabs
+ onSelect={[Function]}
+ selected="risk"
+ tabs={
+ Array [
+ Object {
+ "key": "risk",
+ "label": "hotspot.tabs.risk_description",
+ },
+ Object {
+ "key": "vulnerability",
+ "label": "hotspot.tabs.vulnerability_description",
+ },
+ Object {
+ "key": "fix",
+ "label": "hotspot.tabs.fix_recommendations",
+ },
+ ]
+ }
+ />
+ <div
+ className="boxed-group markdown big-padded"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "<p>This a <strong>strong</strong> message about risk !</p>",
+ }
+ }
+ />
+</Fragment>
+`;
+
+exports[`should render correctly: vulnerability 1`] = `
+<Fragment>
+ <BoxedTabs
+ onSelect={[Function]}
+ selected="vulnerability"
+ tabs={
+ Array [
+ Object {
+ "key": "risk",
+ "label": "hotspot.tabs.risk_description",
+ },
+ Object {
+ "key": "vulnerability",
+ "label": "hotspot.tabs.vulnerability_description",
+ },
+ Object {
+ "key": "fix",
+ "label": "hotspot.tabs.fix_recommendations",
+ },
+ ]
+ }
+ />
+ <div
+ className="boxed-group markdown big-padded"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "<p>This a <strong>strong</strong> message about vulnerability !</p>",
+ }
+ }
+ />
+</Fragment>
+`;
#security_hotspots .main {
flex: 1 0 70%;
overflow-y: auto;
+ background-color: white;
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { groupBy, sortBy } from 'lodash';
-import { RawHotspot, RiskExposure } from '../../types/securityHotspots';
+import { RawHotspot, RiskExposure } from '../../types/security-hotspots';
export const RISK_EXPOSURE_LEVELS = [RiskExposure.HIGH, RiskExposure.MEDIUM, RiskExposure.LOW];
export function groupByCategory(
hotspots: RawHotspot[] = [],
- securityCategories: T.Dict<{ title: string; description?: string }>
+ securityCategories: T.StandardSecurityCategories
) {
const groups = groupBy(hotspots, h => h.securityCategory);
]);
}
-function getCategoryTitle(
- key: string,
- securityCategories: T.Dict<{ title: string; description?: string }>
-) {
+function getCategoryTitle(key: string, securityCategories: T.StandardSecurityCategories) {
return securityCategories[key] ? securityCategories[key].title : key;
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { RawHotspot, RiskExposure } from '../../types/securityHotspots';
+import { ComponentQualifier } from '../../types/component';
+import {
+ DetailedHotspot,
+ DetailedHotspotRule,
+ RawHotspot,
+ RiskExposure
+} from '../../types/security-hotspots';
+import { mockComponent, mockUser } from '../testMocks';
-export function mockHotspot(overrides: Partial<RawHotspot> = {}): RawHotspot {
+export function mockRawHotspot(overrides: Partial<RawHotspot> = {}): RawHotspot {
return {
key: '01fc972e-2a3c-433e-bcae-0bd7f88f5123',
component: 'com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest',
...overrides
};
}
+
+export function mockDetailledHotspot(overrides?: Partial<DetailedHotspot>): DetailedHotspot {
+ return {
+ assignee: mockUser(),
+ author: mockUser(),
+ component: mockComponent({ qualifier: ComponentQualifier.File }),
+ creationDate: '2013-05-13T17:55:41+0200',
+ key: '01fc972e-2a3c-433e-bcae-0bd7f88f5123',
+ line: 142,
+ message: "'3' is a magic number.",
+ project: mockComponent({ qualifier: ComponentQualifier.Project }),
+ resolution: 'FALSE-POSITIVE',
+ rule: mockDetailledHotspotRule(),
+ status: 'RESOLVED',
+ textRange: {
+ startLine: 142,
+ endLine: 142,
+ startOffset: 26,
+ endOffset: 83
+ },
+ updateDate: '2013-05-13T17:55:42+0200',
+ ...overrides
+ };
+}
+
+export function mockDetailledHotspotRule(
+ overrides?: Partial<DetailedHotspotRule>
+): DetailedHotspotRule {
+ return {
+ key: 'squid:S2077',
+ name: 'That rule',
+ fixRecommendations: '<p>This a <strong>strong</strong> message about fixing !</p>',
+ riskDescription: '<p>This a <strong>strong</strong> message about risk !</p>',
+ vulnerabilityDescription: '<p>This a <strong>strong</strong> message about vulnerability !</p>',
+ vulnerabilityProbability: RiskExposure.HIGH,
+ securityCategory: 'sql-injection',
+ ...overrides
+ };
+}
--- /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.
+ */
+export enum RiskExposure {
+ LOW = 'LOW',
+ MEDIUM = 'MEDIUM',
+ HIGH = 'HIGH'
+}
+
+export interface RawHotspot {
+ assignee?: string;
+ author?: string;
+ component: string;
+ creationDate: string;
+ key: string;
+ line?: number;
+ message: string;
+ project: string;
+ resolution: string;
+ rule: string;
+ securityCategory: string;
+ status: string;
+ subProject?: string;
+ updateDate: string;
+ vulnerabilityProbability: RiskExposure;
+}
+
+export interface DetailedHotspot {
+ assignee?: Pick<T.UserBase, 'active' | 'login' | 'name'>;
+ author?: Pick<T.UserBase, 'login'>;
+ component: T.Component;
+ creationDate: string;
+ key: string;
+ line?: number;
+ message: string;
+ project: T.Component;
+ resolution: string;
+ rule: DetailedHotspotRule;
+ status: string;
+ textRange: T.TextRange;
+ updateDate: string;
+}
+
+export interface DetailedHotspotRule {
+ fixRecommendations?: string;
+ key: string;
+ name: string;
+ riskDescription?: string;
+ securityCategory: string;
+ vulnerabilityDescription?: string;
+ vulnerabilityProbability: RiskExposure;
+}
+
+export interface HotspotSearchResponse {
+ components?: { key: string; qualifier: string; name: string }[];
+ hotspots: RawHotspot[];
+ paging: T.Paging;
+}
+++ /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.
- */
-export enum RiskExposure {
- LOW = 'LOW',
- MEDIUM = 'MEDIUM',
- HIGH = 'HIGH'
-}
-
-export interface RawHotspot {
- assignee?: string;
- author?: string;
- component: string;
- creationDate: string;
- key: string;
- line?: number;
- message: string;
- project: string;
- resolution: string;
- rule: string;
- securityCategory: string;
- updateDate: string;
- vulnerabilityProbability: RiskExposure;
- status: string;
- subProject?: string;
-}
-
-export interface HotspotSearchResponse {
- components?: { key: string; qualifier: string; name: string }[];
- hotspots: RawHotspot[];
- paging: T.Paging;
-}
uuid: string;
}
+ export type StandardSecurityCategories = T.Dict<{ title: string; description?: string }>;
+
export type Standards = {
[key in StandardType]: T.Dict<{ title: string; description?: string }>;
};
hotspots.list_title.REVIEWED={0} reviewed Security Hotspots
hotspots.risk_exposure=Review priority:
+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?
+
#------------------------------------------------------------------------------
#
# ISSUES