aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps
diff options
context:
space:
mode:
author7PH <b.raymond@protonmail.com>2024-09-04 14:01:21 +0200
committersonartech <sonartech@sonarsource.com>2024-09-12 20:02:55 +0000
commit4674af96f13e9f48a6a371ddd07c21ae2859c66d (patch)
tree29bdff5a8254d815bb1d8da1454d949cd88c9b9c /server/sonar-web/src/main/js/apps
parent7ffdf0a08ac0557bcf1c29035500bc0f7cabed3a (diff)
downloadsonarqube-4674af96f13e9f48a6a371ddd07c21ae2859c66d.tar.gz
sonarqube-4674af96f13e9f48a6a371ddd07c21ae2859c66d.zip
SONAR-22914 Render CVE information in Issues and Security Hotspots pages
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx50
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/issues/test-utils.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-it.tsx46
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainer.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx69
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetHeader.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx19
11 files changed, 176 insertions, 49 deletions
diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
index 09cb5e4f844..d341530762b 100644
--- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
@@ -24,12 +24,13 @@ import React from 'react';
import { byRole, byText } from '~sonar-aligned/helpers/testSelector';
import { ISSUE_101 } from '../../../api/mocks/data/ids';
import { TabKeys } from '../../../components/rules/RuleTabViewer';
-import { mockCurrentUser, mockLoggedInUser } from '../../../helpers/testMocks';
+import { mockCurrentUser, mockCve, mockLoggedInUser } from '../../../helpers/testMocks';
import { Feature } from '../../../types/features';
import { RestUserDetailed } from '../../../types/users';
import {
branchHandler,
componentsHandler,
+ cveHandler,
issuesHandler,
renderIssueApp,
renderProjectIssuesApp,
@@ -63,6 +64,7 @@ jest.mock('../../../components/common/ScreenPositionHelper', () => ({
beforeEach(() => {
issuesHandler.reset();
+ cveHandler.reset();
componentsHandler.reset();
branchHandler.reset();
usersHandler.reset();
@@ -211,6 +213,52 @@ describe('issue app', () => {
expect(screen.getByRole('heading', { name: 'Defense-In-Depth', level: 3 })).toBeInTheDocument();
});
+ it('should render CVE details', async () => {
+ const user = userEvent.setup();
+ renderProjectIssuesApp('project/issues?issues=issue2&open=issue2&id=myproject');
+
+ await user.click(
+ await screen.findByRole('tab', { name: 'coding_rules.description_section.title.root_cause' }),
+ );
+
+ await user.click(screen.getByRole('radio', { name: 'coding_rules.description_context.other' }));
+
+ expect(await screen.findByRole('heading', { name: 'CVE-2021-12345' })).toBeInTheDocument();
+
+ const rows = byRole('row').getAll(ui.cveTable.get());
+ expect(rows).toHaveLength(4);
+ expect(byText('CWE-79, CWE-89').get(rows[0])).toBeInTheDocument();
+ expect(byText('rule.cve_details.epss_score.value.20.56').get(rows[1])).toBeInTheDocument();
+ expect(byText('0.3').get(rows[2])).toBeInTheDocument();
+ expect(byText('Oct 04, 2021').get(rows[3])).toBeInTheDocument();
+ });
+
+ it('should not render CVE CVSS and CWEs when not set', async () => {
+ const user = userEvent.setup();
+ cveHandler.setCveList([
+ mockCve({
+ cvssScore: undefined,
+ cwes: [],
+ }),
+ ]);
+ renderProjectIssuesApp('project/issues?issues=issue2&open=issue2&id=myproject');
+
+ await user.click(
+ await screen.findByRole('tab', { name: 'coding_rules.description_section.title.root_cause' }),
+ );
+
+ await user.click(
+ await screen.findByRole('radio', { name: 'coding_rules.description_context.other' }),
+ );
+
+ expect(await screen.findByRole('heading', { name: 'CVE-2021-12345' })).toBeInTheDocument();
+
+ const rows = byRole('row').getAll(ui.cveTable.get());
+ expect(rows).toHaveLength(2);
+ expect(byText('rule.cve_details.epss_score.value.20.56').get(rows[0])).toBeInTheDocument();
+ expect(byText('Oct 04, 2021').get(rows[1])).toBeInTheDocument();
+ });
+
it('should be able to change the issue status', async () => {
const user = userEvent.setup();
issuesHandler.setIsAdmin(true);
diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx
index 2ce9343bb76..30af209bcb4 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx
@@ -41,6 +41,7 @@ import { getBranchLikeQuery, isPullRequest } from '~sonar-aligned/helpers/branch
import { isPortfolioLike } from '~sonar-aligned/helpers/component';
import { ComponentQualifier } from '~sonar-aligned/types/component';
import { Location, RawQuery, Router } from '~sonar-aligned/types/router';
+import { getCve } from '../../../api/cves';
import { listIssues, searchIssues } from '../../../api/issues';
import { getRuleDetails } from '../../../api/rules';
import withComponentContext from '../../../app/components/componentContext/withComponentContext';
@@ -66,6 +67,7 @@ import { serializeDate } from '../../../helpers/query';
import { withBranchLikes } from '../../../queries/branch';
import { BranchLike } from '../../../types/branch-like';
import { isProject } from '../../../types/component';
+import { Cve } from '../../../types/cves';
import {
ASSIGNEE_ME,
Facet,
@@ -122,6 +124,7 @@ export interface State {
bulkChangeModal: boolean;
checkAll?: boolean;
checked: string[];
+ cve?: Cve;
effortTotal?: number;
facets: Dict<Facet>;
issues: Issue[];
@@ -383,8 +386,13 @@ export class App extends React.PureComponent<Props, State> {
.then((response) => response.rule)
.catch(() => undefined);
+ let cve: Cve | undefined;
+ if (typeof openIssue.cveId === 'string') {
+ cve = await getCve(openIssue.cveId);
+ }
+
if (this.mounted) {
- this.setState({ loadingRule: false, openRuleDetails });
+ this.setState({ loadingRule: false, openRuleDetails, cve });
}
}
@@ -1211,7 +1219,7 @@ export class App extends React.PureComponent<Props, State> {
}
renderPage() {
- const { openRuleDetails, checkAll, issues, loading, openIssue, paging, loadingRule } =
+ const { openRuleDetails, cve, checkAll, issues, loading, openIssue, paging, loadingRule } =
this.state;
return (
@@ -1259,6 +1267,7 @@ export class App extends React.PureComponent<Props, State> {
onIssueChange={this.handleIssueChange}
ruleDescriptionContextKey={openIssue.ruleDescriptionContextKey}
ruleDetails={openRuleDetails}
+ cve={cve}
selectedFlowIndex={this.state.selectedFlowIndex}
selectedLocationIndex={this.state.selectedLocationIndex}
/>
diff --git a/server/sonar-web/src/main/js/apps/issues/test-utils.tsx b/server/sonar-web/src/main/js/apps/issues/test-utils.tsx
index 995d63c8259..3918108f48b 100644
--- a/server/sonar-web/src/main/js/apps/issues/test-utils.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/test-utils.tsx
@@ -23,6 +23,7 @@ import { Outlet, Route } from 'react-router-dom';
import { byPlaceholderText, byRole, byTestId, byText } from '~sonar-aligned/helpers/testSelector';
import BranchesServiceMock from '../../api/mocks/BranchesServiceMock';
import ComponentsServiceMock from '../../api/mocks/ComponentsServiceMock';
+import CveServiceMock from '../../api/mocks/CveServiceMock';
import FixIssueServiceMock from '../../api/mocks/FixIssueServiceMock';
import IssuesServiceMock from '../../api/mocks/IssuesServiceMock';
import SourcesServiceMock from '../../api/mocks/SourcesServiceMock';
@@ -43,6 +44,7 @@ import { projectIssuesRoutes } from './routes';
export const usersHandler = new UsersServiceMock();
export const issuesHandler = new IssuesServiceMock(usersHandler);
+export const cveHandler = new CveServiceMock();
export const componentsHandler = new ComponentsServiceMock();
export const sourcesHandler = new SourcesServiceMock();
export const branchHandler = new BranchesServiceMock();
@@ -142,6 +144,8 @@ export const ui = {
vulnerabilityIssueTypeFilter: byRole('checkbox', { name: 'issue.type.VULNERABILITY' }),
prioritizedRuleFilter: byRole('checkbox', { name: 'issues.facet.prioritized_rule' }),
+ cveTable: byRole('table', { name: 'rule.cve_details' }),
+
bulkChangeComment: byRole('textbox', { name: /issue_bulk_change.resolution_comment/ }),
clearAllFilters: byRole('button', { name: 'clear_all_filters' }),
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx
index 0380c756328..9d64bf4a634 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx
@@ -244,6 +244,7 @@ export default function SecurityHotspotsAppRenderer(props: SecurityHotspotsAppRe
<HotspotViewer
component={component}
hotspotKey={selectedHotspot.key}
+ cveId={selectedHotspot.cveId}
hotspotsReviewedMeasure={hotspotsReviewedMeasure}
onLocationClick={props.onLocationClick}
onSwitchStatusFilter={props.onSwitchStatusFilter}
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-it.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-it.tsx
index 4fef8864a5f..8e4e74463e8 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-it.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-it.tsx
@@ -25,13 +25,14 @@ import { byDisplayValue, byRole, byTestId, byText } from '~sonar-aligned/helpers
import { MetricKey } from '~sonar-aligned/types/metrics';
import BranchesServiceMock from '../../../api/mocks/BranchesServiceMock';
import CodingRulesServiceMock from '../../../api/mocks/CodingRulesServiceMock';
+import CveServiceMock from '../../../api/mocks/CveServiceMock';
import SecurityHotspotServiceMock from '../../../api/mocks/SecurityHotspotServiceMock';
import { getSecurityHotspots, setSecurityHotspotStatus } from '../../../api/security-hotspots';
import { getUsers } from '../../../api/users';
import { mockComponent } from '../../../helpers/mocks/component';
import { openHotspot, probeSonarLintServers } from '../../../helpers/sonarlint';
import { get, save } from '../../../helpers/storage';
-import { mockLoggedInUser } from '../../../helpers/testMocks';
+import { mockCve, mockLoggedInUser } from '../../../helpers/testMocks';
import { renderAppWithComponentContext } from '../../../helpers/testReactTestingUtils';
import { ComponentContextShape } from '../../../types/component';
import SecurityHotspotsApp from '../SecurityHotspotsApp';
@@ -85,6 +86,7 @@ const ui = {
filterToReview: byRole('radio', { name: 'hotspot.filters.status.to_review' }),
fixContent: byText('This is how to fix'),
fixTab: byRole('tab', { name: /hotspots.tabs.fix_recommendations/ }),
+ cveTable: byRole('table', { name: 'rule.cve_details' }),
hotpostListTitle: byText('hotspots.list_title'),
hotspotCommentBox: byRole('textbox', { name: 'hotspots.comment.field' }),
hotspotStatus: byRole('heading', { name: 'status: hotspots.status_option.FIXED' }),
@@ -107,6 +109,7 @@ const ui = {
const originalScrollTo = window.scrollTo;
const hotspotsHandler = new SecurityHotspotServiceMock();
+const cveHandler = new CveServiceMock();
const rulesHandles = new CodingRulesServiceMock();
const branchHandler = new BranchesServiceMock();
@@ -147,6 +150,7 @@ beforeEach(() => {
afterEach(() => {
hotspotsHandler.reset();
+ cveHandler.reset();
rulesHandles.reset();
branchHandler.reset();
});
@@ -187,6 +191,46 @@ describe('rendering', () => {
expect(await ui.reviewButton.findAll()).toHaveLength(2);
});
+
+ it('should render CVE details', async () => {
+ const user = userEvent.setup();
+
+ renderSecurityHotspotsApp(
+ 'security_hotspots?id=guillaume-peoch-sonarsource_benflix_AYGpXq2bd8qy4i0eO9ed&hotspots=test-cve',
+ );
+
+ await user.click(await ui.riskTab.find());
+ expect(await screen.findByRole('heading', { name: 'CVE-2021-12345' })).toBeInTheDocument();
+
+ const rows = byRole('row').getAll(ui.cveTable.get());
+ expect(rows).toHaveLength(4);
+ expect(byText('CWE-79, CWE-89').get(rows[0])).toBeInTheDocument();
+ expect(byText('rule.cve_details.epss_score.value.20.56').get(rows[1])).toBeInTheDocument();
+ expect(byText('0.3').get(rows[2])).toBeInTheDocument();
+ expect(byText('Oct 04, 2021').get(rows[3])).toBeInTheDocument();
+ });
+
+ it('should not render CVE CVSS and CWEs when not set', async () => {
+ const user = userEvent.setup();
+ cveHandler.setCveList([
+ mockCve({
+ cvssScore: undefined,
+ cwes: [],
+ }),
+ ]);
+
+ renderSecurityHotspotsApp(
+ 'security_hotspots?id=guillaume-peoch-sonarsource_benflix_AYGpXq2bd8qy4i0eO9ed&hotspots=test-cve',
+ );
+
+ await user.click(await ui.riskTab.find());
+ expect(await screen.findByRole('heading', { name: 'CVE-2021-12345' })).toBeInTheDocument();
+
+ const rows = byRole('row').getAll(ui.cveTable.get());
+ expect(rows).toHaveLength(2);
+ expect(byText('rule.cve_details.epss_score.value.20.56').get(rows[0])).toBeInTheDocument();
+ expect(byText('Oct 04, 2021').get(rows[1])).toBeInTheDocument();
+ });
});
describe('CRUD', () => {
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainer.tsx
index 8f301649c64..247f2f0bd60 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainer.tsx
@@ -197,7 +197,7 @@ export default class HotspotSnippetContainer extends React.Component<Props, Stat
};
render() {
- const { hotspot, selectedHotspotLocation } = this.props;
+ const { branchLike, component, hotspot, selectedHotspotLocation } = this.props;
const { highlightedSymbols, lastLine, loading, sourceLines, secondaryLocations } = this.state;
const locations = locationsByLine([hotspot]);
@@ -206,6 +206,8 @@ export default class HotspotSnippetContainer extends React.Component<Props, Stat
return (
<HotspotSnippetContainerRenderer
+ component={component}
+ branchLike={branchLike}
highlightedSymbols={highlightedSymbols}
hotspot={hotspot}
loading={loading}
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx
index 43179dd7c44..bca90e9df1b 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx
@@ -19,11 +19,14 @@
*/
import { withTheme } from '@emotion/react';
import styled from '@emotion/styled';
-import { Spinner, themeColor } from 'design-system';
+import { Spinner } from '@sonarsource/echoes-react';
+import { FlagMessage, themeColor } from 'design-system';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';
+import { BranchLike } from '../../../types/branch-like';
import { Hotspot } from '../../../types/security-hotspots';
import {
+ Component,
ExpandDirection,
FlowLocation,
LinearIssueLocation,
@@ -32,8 +35,11 @@ import {
} from '../../../types/types';
import SnippetViewer from '../../issues/crossComponentSourceViewer/SnippetViewer';
import HotspotPrimaryLocationBox from './HotspotPrimaryLocationBox';
+import HotspotSnippetHeader from './HotspotSnippetHeader';
export interface HotspotSnippetContainerRendererProps {
+ branchLike?: BranchLike;
+ component: Component;
highlightedSymbols: string[];
hotspot: Hotspot;
loading: boolean;
@@ -113,6 +119,8 @@ export default function HotspotSnippetContainerRenderer(
selectedHotspotLocation,
sourceLines,
sourceViewerFile,
+ component,
+ branchLike,
} = props;
const scrollableRef = React.useRef<HTMLDivElement>(null);
@@ -139,37 +147,38 @@ export default function HotspotSnippetContainerRenderer(
: undefined;
return (
- <>
- {!loading && sourceLines.length === 0 && (
- <p className="sw-my-4">{translate('hotspots.no_associated_lines')}</p>
+ <Spinner isLoading={loading}>
+ {sourceLines.length === 0 && (
+ <FlagMessage variant="info">{translate('hotspots.no_associated_lines')}</FlagMessage>
)}
- <SourceFileWrapper className="sw-box-border sw-w-full sw-rounded-1" ref={scrollableRef}>
- <Spinner className="sw-m-4" loading={loading} />
-
- {!loading && sourceLines.length > 0 && (
- <SnippetViewer
- component={sourceViewerFile}
- displayLineNumberOptions={false}
- displaySCM={false}
- expandBlock={(_i, direction) =>
- animateExpansion(scrollableRef, props.onExpandBlock, direction)
- }
- handleSymbolClick={props.onSymbolClick}
- highlightedLocationMessage={highlightedLocation}
- highlightedSymbols={highlightedSymbols}
- index={0}
- locations={secondaryLocations}
- locationsByLine={primaryLocations}
- onLocationSelect={props.onLocationSelect}
- renderAdditionalChildInLine={renderHotspotBoxInLine}
- renderDuplicationPopup={noop}
- snippet={sourceLines}
- hideLocationIndex={secondaryLocations.length !== 0}
- />
- )}
- </SourceFileWrapper>
- </>
+ {sourceLines.length > 0 && (
+ <>
+ <HotspotSnippetHeader hotspot={hotspot} component={component} branchLike={branchLike} />
+ <SourceFileWrapper className="sw-box-border sw-w-full sw-rounded-1" ref={scrollableRef}>
+ <SnippetViewer
+ component={sourceViewerFile}
+ displayLineNumberOptions={false}
+ displaySCM={false}
+ expandBlock={(_i, direction) =>
+ animateExpansion(scrollableRef, props.onExpandBlock, direction)
+ }
+ handleSymbolClick={props.onSymbolClick}
+ highlightedLocationMessage={highlightedLocation}
+ highlightedSymbols={highlightedSymbols}
+ index={0}
+ locations={secondaryLocations}
+ locationsByLine={primaryLocations}
+ onLocationSelect={props.onLocationSelect}
+ renderAdditionalChildInLine={renderHotspotBoxInLine}
+ renderDuplicationPopup={noop}
+ snippet={sourceLines}
+ hideLocationIndex={secondaryLocations.length !== 0}
+ />
+ </SourceFileWrapper>
+ </>
+ )}
+ </Spinner>
);
}
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetHeader.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetHeader.tsx
index 29add9012d1..84db10765de 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetHeader.tsx
@@ -52,7 +52,7 @@ function HotspotSnippetHeader(props: HotspotSnippetHeaderProps) {
return (
<StyledHeader
- className={`sw-box-border sw-flex sw-gap-2 sw-justify-between -sw-mb-4 sw-mt-6 sw-px-4
+ className={`sw-box-border sw-flex sw-gap-2 sw-justify-between sw-mt-6 sw-px-4
sw-py-3`}
>
<Note className="sw-flex sw-flex-1 sw-flex-wrap sw-gap-2 sw-items-center sw-my-1/2">
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx
index 2940d29cd44..30efacc1fdd 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx
@@ -18,9 +18,11 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { getCve } from '../../../api/cves';
import { getRuleDetails } from '../../../api/rules';
import { getSecurityHotspotDetails } from '../../../api/security-hotspots';
import { get } from '../../../helpers/storage';
+import { Cve } from '../../../types/cves';
import { Standards } from '../../../types/security';
import {
Hotspot,
@@ -35,6 +37,7 @@ import HotspotViewerRenderer from './HotspotViewerRenderer';
interface Props {
component: Component;
+ cveId?: string;
hotspotKey: string;
hotspotsReviewedMeasure?: string;
onLocationClick: (index: number) => void;
@@ -45,6 +48,7 @@ interface Props {
}
interface State {
+ cve?: Cve;
hotspot?: Hotspot;
lastStatusChangedTo?: HotspotStatusOption;
loading: boolean;
@@ -83,6 +87,10 @@ export default class HotspotViewer extends React.PureComponent<Props, State> {
try {
const hotspot = await getSecurityHotspotDetails(this.props.hotspotKey);
const ruleDetails = await getRuleDetails({ key: hotspot.rule.key }).then((r) => r.rule);
+ let cve;
+ if (typeof this.props.cveId === 'string') {
+ cve = await getCve(this.props.cveId);
+ }
if (this.mounted) {
this.setState({
@@ -90,6 +98,7 @@ export default class HotspotViewer extends React.PureComponent<Props, State> {
loading: false,
ruleLanguage: ruleDetails.lang,
ruleDescriptionSections: ruleDetails.descriptionSections,
+ cve,
});
}
} catch (error) {
@@ -132,6 +141,7 @@ export default class HotspotViewer extends React.PureComponent<Props, State> {
hotspot,
ruleDescriptionSections,
ruleLanguage,
+ cve,
loading,
showStatusUpdateSuccessModal,
lastStatusChangedTo,
@@ -150,6 +160,7 @@ export default class HotspotViewer extends React.PureComponent<Props, State> {
onUpdateHotspot={this.handleHotspotUpdate}
ruleDescriptionSections={ruleDescriptionSections}
ruleLanguage={ruleLanguage}
+ cve={cve}
selectedHotspotLocation={selectedHotspotLocation}
showStatusUpdateSuccessModal={showStatusUpdateSuccessModal}
standards={standards}
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx
index 1ed1a6b62ec..acc3a9e4fdd 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx
@@ -26,6 +26,7 @@ import { Component } from '../../../types/types';
import { HotspotHeader } from './HotspotHeader';
import { Spinner } from 'design-system';
+import { Cve } from '../../../types/cves';
import { CurrentUser } from '../../../types/users';
import { RuleDescriptionSection } from '../../coding-rules/rule';
import HotspotReviewHistoryAndComments from './HotspotReviewHistoryAndComments';
@@ -37,6 +38,7 @@ import StatusUpdateSuccessModal from './StatusUpdateSuccessModal';
export interface HotspotViewerRendererProps {
component: Component;
currentUser: CurrentUser;
+ cve?: Cve;
hotspot?: Hotspot;
hotspotsReviewedMeasure?: string;
lastStatusChangedTo?: HotspotStatusOption;
@@ -62,6 +64,7 @@ export function HotspotViewerRenderer(props: HotspotViewerRendererProps) {
loading,
ruleDescriptionSections,
ruleLanguage,
+ cve,
selectedHotspotLocation,
showStatusUpdateSuccessModal,
standards,
@@ -99,8 +102,6 @@ export function HotspotViewerRenderer(props: HotspotViewerRendererProps) {
onCommentUpdate={props.onUpdateHotspot}
/>
}
- branchLike={branchLike}
- component={component}
codeTabContent={
<HotspotSnippetContainer
branchLike={branchLike}
@@ -114,6 +115,7 @@ export function HotspotViewerRenderer(props: HotspotViewerRendererProps) {
onUpdateHotspot={props.onUpdateHotspot}
ruleDescriptionSections={ruleDescriptionSections}
ruleLanguage={ruleLanguage}
+ cve={cve}
/>
</div>
)}
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx
index 5044e1aba97..a6e43d589fa 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx
@@ -34,19 +34,16 @@ import { isInput, isShortcut } from '../../../helpers/keyboardEventHelpers';
import { KeyboardKeys } from '../../../helpers/keycodes';
import { translate } from '../../../helpers/l10n';
import { useRefreshBranchStatus } from '../../../queries/branch';
-import { BranchLike } from '../../../types/branch-like';
+import { Cve } from '../../../types/cves';
import { Hotspot, HotspotStatusOption } from '../../../types/security-hotspots';
-import { Component } from '../../../types/types';
import { RuleDescriptionSection, RuleDescriptionSections } from '../../coding-rules/rule';
import useStickyDetection from '../hooks/useStickyDetection';
-import HotspotSnippetHeader from './HotspotSnippetHeader';
import StatusReviewButton from './status/StatusReviewButton';
interface Props {
activityTabContent: React.ReactNode;
- branchLike?: BranchLike;
codeTabContent: React.ReactNode;
- component: Component;
+ cve: Cve | undefined;
hotspot: Hotspot;
onUpdateHotspot: (statusUpdate?: boolean, statusOption?: HotspotStatusOption) => Promise<void>;
ruleDescriptionSections?: RuleDescriptionSection[];
@@ -76,8 +73,7 @@ export default function HotspotViewerTabs(props: Props) {
hotspot,
ruleDescriptionSections,
ruleLanguage,
- component,
- branchLike,
+ cve,
} = props;
const refreshBranchStatus = useRefreshBranchStatus(component.key);
@@ -206,9 +202,6 @@ export default function HotspotViewerTabs(props: Props) {
/>
{isSticky && <StatusReviewButton hotspot={hotspot} onStatusChange={handleStatusChange} />}
</div>
- {currentTab.value === TabKeys.Code && codeTabContent && (
- <HotspotSnippetHeader hotspot={hotspot} component={component} branchLike={branchLike} />
- )}
</StickyTabs>
<div
aria-labelledby={getTabId(currentTab.value)}
@@ -219,7 +212,11 @@ export default function HotspotViewerTabs(props: Props) {
{currentTab.value === TabKeys.Code && codeTabContent}
{currentTab.value === TabKeys.RiskDescription && rootCauseDescriptionSections && (
- <RuleDescription language={ruleLanguage} sections={rootCauseDescriptionSections} />
+ <RuleDescription
+ language={ruleLanguage}
+ sections={rootCauseDescriptionSections}
+ cve={cve}
+ />
)}
{currentTab.value === TabKeys.VulnerabilityDescription &&