Просмотр исходного кода

SONAR-13033 security review rating in overview

tags/8.2.0.32929
Jeremy 4 лет назад
Родитель
Сommit
3ac2de9977

+ 72
- 67
server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanel.tsx Просмотреть файл

@@ -38,6 +38,7 @@ import { IssueType, MeasurementType } from '../utils';
import DebtValue from './DebtValue';
import { DrilldownMeasureValue } from './DrilldownMeasureValue';
import { LeakPeriodInfo } from './LeakPeriodInfo';
import SecurityHotspotsReviewed from './SecurityHotspotsReviewed';

export interface MeasuresPanelProps {
branchLike?: BranchLike;
@@ -60,9 +61,11 @@ export function MeasuresPanel(props: MeasuresPanelProps) {

const [tab, selectTab] = React.useState(MeasuresPanelTabs.New);

const isNewCodeTab = tab === MeasuresPanelTabs.New;

React.useEffect(() => {
// Open Overall tab by default if there are no new measures.
if (loading === false && !hasDiffMeasures && tab === MeasuresPanelTabs.New) {
if (loading === false && !hasDiffMeasures && isNewCodeTab) {
selectTab(MeasuresPanelTabs.Overall);
}
// In this case, we explicitly do NOT want to mark tab as a dependency, as
@@ -105,7 +108,7 @@ export function MeasuresPanel(props: MeasuresPanelProps) {
<BoxedTabs onSelect={selectTab} selected={tab} tabs={tabs} />

<div className="overview-panel-content flex-1 bordered">
{!hasDiffMeasures && tab === MeasuresPanelTabs.New ? (
{!hasDiffMeasures && isNewCodeTab ? (
<div
className="display-flex-center display-flex-justify-center"
style={{ height: 500 }}>
@@ -136,73 +139,75 @@ export function MeasuresPanel(props: MeasuresPanelProps) {
</div>
) : (
<>
{[IssueType.Bug, IssueType.Vulnerability, IssueType.CodeSmell].map(
(type: IssueType) => (
<div
className="display-flex-row overview-measures-row"
data-test={`overview__measures-${type.toString().toLowerCase()}`}
key={type}>
{type === IssueType.CodeSmell ? (
<>
<div className="overview-panel-big-padded flex-1 small display-flex-center big-spacer-left">
<DebtValue
branchLike={branchLike}
component={component}
measures={measures}
useDiffMetric={tab === MeasuresPanelTabs.New}
/>
</div>
<div className="flex-1 small display-flex-center">
<IssueLabel
branchLike={branchLike}
component={component}
measures={measures}
type={type}
useDiffMetric={tab === MeasuresPanelTabs.New}
/>
</div>
</>
) : (
<>
<div className="overview-panel-big-padded flex-1 small display-flex-center big-spacer-left">
<IssueLabel
branchLike={branchLike}
component={component}
measures={measures}
type={type}
useDiffMetric={tab === MeasuresPanelTabs.New}
/>
</div>
{type === 'VULNERABILITY' && (
<div className="flex-1 small display-flex-center">
<IssueLabel
branchLike={branchLike}
component={component}
docTooltip={import(
/* webpackMode: "eager" */ 'Docs/tooltips/metrics/security-hotspots.md'
)}
measures={measures}
type={IssueType.SecurityHotspot}
useDiffMetric={tab === MeasuresPanelTabs.New}
/>
</div>
)}
</>
)}
{(!isApp || tab === MeasuresPanelTabs.Overall) && (
<div className="overview-panel-big-padded overview-measures-aside display-flex-center">
<IssueRating
{[
IssueType.Bug,
IssueType.Vulnerability,
IssueType.SecurityHotspot,
IssueType.CodeSmell
].map((type: IssueType) => (
<div
className="display-flex-row overview-measures-row"
data-test={`overview__measures-${type.toString().toLowerCase()}`}
key={type}>
{type === IssueType.CodeSmell ? (
<>
<div className="overview-panel-big-padded flex-1 small display-flex-center big-spacer-left">
<DebtValue
branchLike={branchLike}
component={component}
measures={measures}
useDiffMetric={isNewCodeTab}
/>
</div>
<div className="flex-1 small display-flex-center">
<IssueLabel
branchLike={branchLike}
component={component}
measures={measures}
type={type}
useDiffMetric={tab === MeasuresPanelTabs.New}
useDiffMetric={isNewCodeTab}
/>
</div>
)}
</div>
)
)}
</>
) : (
<div className="overview-panel-big-padded flex-1 small display-flex-center big-spacer-left">
<IssueLabel
branchLike={branchLike}
component={component}
docTooltip={
type === IssueType.SecurityHotspot
? import(
/* webpackMode: "eager" */ 'Docs/tooltips/metrics/security-hotspots.md'
)
: undefined
}
measures={measures}
type={type}
useDiffMetric={isNewCodeTab}
/>
</div>
)}
{type === IssueType.SecurityHotspot && (
<div className="flex-1 small display-flex-center">
<SecurityHotspotsReviewed
measures={measures}
useDiffMetric={isNewCodeTab}
/>
</div>
)}
{(!isApp || tab === MeasuresPanelTabs.Overall) && (
<div className="overview-panel-big-padded overview-measures-aside display-flex-center">
<IssueRating
branchLike={branchLike}
component={component}
measures={measures}
type={type}
useDiffMetric={isNewCodeTab}
/>
</div>
)}
</div>
))}

<div className="display-flex-row overview-measures-row">
{(findMeasure(measures, MetricKey.coverage) ||
@@ -212,11 +217,11 @@ export function MeasuresPanel(props: MeasuresPanelProps) {
data-test="overview__measures-coverage">
<MeasurementLabel
branchLike={branchLike}
centered={tab === MeasuresPanelTabs.New}
centered={isNewCodeTab}
component={component}
measures={measures}
type={MeasurementType.Coverage}
useDiffMetric={tab === MeasuresPanelTabs.New}
useDiffMetric={isNewCodeTab}
/>

{tab === MeasuresPanelTabs.Overall && (
@@ -234,11 +239,11 @@ export function MeasuresPanel(props: MeasuresPanelProps) {
<div className="overview-panel-huge-padded flex-1 display-flex-center">
<MeasurementLabel
branchLike={branchLike}
centered={tab === MeasuresPanelTabs.New}
centered={isNewCodeTab}
component={component}
measures={measures}
type={MeasurementType.Duplication}
useDiffMetric={tab === MeasuresPanelTabs.New}
useDiffMetric={isNewCodeTab}
/>

{tab === MeasuresPanelTabs.Overall && (

+ 56
- 0
server/sonar-web/src/main/js/apps/overview/branches/SecurityHotspotsReviewed.tsx Просмотреть файл

@@ -0,0 +1,56 @@
/*
* 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 } from 'sonar-ui-common/helpers/l10n';
import { formatMeasure } from 'sonar-ui-common/helpers/measures';
import { getLeakValue } from '../../../components/measure/utils';
import { findMeasure } from '../../../helpers/measures';
import { MetricKey } from '../../../types/metrics';

export interface SecurityHotspotsReviewedProps {
measures: T.MeasureEnhanced[];
useDiffMetric?: boolean;
}

export default function SecurityHotspotsReviewed(props: SecurityHotspotsReviewedProps) {
const { measures, useDiffMetric = false } = props;
const metric = useDiffMetric
? MetricKey.new_security_hotspots_reviewed
: MetricKey.security_hotspots_reviewed;
const measure = findMeasure(measures, metric);

let value;
if (measure) {
value = useDiffMetric ? getLeakValue(measure) : measure.value;
}

return (
<>
{value === undefined ? (
<span aria-label={translate('no_data')} className="overview-measures-empty-value" />
) : (
<span className="huge">{formatMeasure(value, 'PERCENT')}</span>
)}
<span className="big-spacer-left">
{translate('overview.measures.security_hotspots_reviewed')}
</span>
</>
);
}

+ 46
- 0
server/sonar-web/src/main/js/apps/overview/branches/__tests__/SecurityHotspotsReviewed-test.tsx Просмотреть файл

@@ -0,0 +1,46 @@
/*
* 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 { mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks';
import { MetricKey } from '../../../../types/metrics';
import SecurityHotspotsReviewed, {
SecurityHotspotsReviewedProps
} from '../SecurityHotspotsReviewed';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
expect(shallowRender({ useDiffMetric: true })).toMatchSnapshot('on new code');
expect(shallowRender({ measures: [] })).toMatchSnapshot('no measures');
});

function shallowRender(props: Partial<SecurityHotspotsReviewedProps> = {}) {
return shallow(
<SecurityHotspotsReviewed
measures={[
mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.security_hotspots_reviewed }) }),
mockMeasureEnhanced({
metric: mockMetric({ key: MetricKey.new_security_hotspots_reviewed })
})
]}
{...props}
/>
);
}

+ 164
- 0
server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/BranchOverview-test.tsx.snap Просмотреть файл

@@ -268,6 +268,64 @@ exports[`application overview should render correctly 1`] = `
],
"value": "1.0",
},
Object {
"bestValue": true,
"metric": Object {
"id": "security_hotspots_reviewed",
"key": "security_hotspots_reviewed",
"name": "security_hotspots_reviewed",
"type": "INT",
},
"periods": undefined,
"value": "1.0",
},
Object {
"bestValue": true,
"leak": "1",
"metric": Object {
"id": "new_security_hotspots_reviewed",
"key": "new_security_hotspots_reviewed",
"name": "new_security_hotspots_reviewed",
"type": "INT",
},
"periods": Array [
Object {
"bestValue": true,
"index": 1,
"value": "1.0",
},
],
"value": "1.0",
},
Object {
"bestValue": true,
"metric": Object {
"id": "security_review_rating",
"key": "security_review_rating",
"name": "security_review_rating",
"type": "RATING",
},
"periods": undefined,
"value": "1.0",
},
Object {
"bestValue": true,
"leak": "1",
"metric": Object {
"id": "new_security_review_rating",
"key": "new_security_review_rating",
"name": "new_security_review_rating",
"type": "RATING",
},
"periods": Array [
Object {
"bestValue": true,
"index": 1,
"value": "1.0",
},
],
"value": "1.0",
},
Object {
"bestValue": true,
"metric": Object {
@@ -660,6 +718,30 @@ exports[`application overview should render correctly 1`] = `
"name": "new_security_hotspots",
"type": "INT",
},
Object {
"id": "security_hotspots_reviewed",
"key": "security_hotspots_reviewed",
"name": "security_hotspots_reviewed",
"type": "INT",
},
Object {
"id": "new_security_hotspots_reviewed",
"key": "new_security_hotspots_reviewed",
"name": "new_security_hotspots_reviewed",
"type": "INT",
},
Object {
"id": "security_review_rating",
"key": "security_review_rating",
"name": "security_review_rating",
"type": "RATING",
},
Object {
"id": "new_security_review_rating",
"key": "new_security_review_rating",
"name": "new_security_review_rating",
"type": "RATING",
},
Object {
"id": "code_smells",
"key": "code_smells",
@@ -1131,6 +1213,64 @@ exports[`project overview should render correctly 1`] = `
],
"value": "1.0",
},
Object {
"bestValue": true,
"metric": Object {
"id": "security_hotspots_reviewed",
"key": "security_hotspots_reviewed",
"name": "security_hotspots_reviewed",
"type": "INT",
},
"periods": undefined,
"value": "1.0",
},
Object {
"bestValue": true,
"leak": "1",
"metric": Object {
"id": "new_security_hotspots_reviewed",
"key": "new_security_hotspots_reviewed",
"name": "new_security_hotspots_reviewed",
"type": "INT",
},
"periods": Array [
Object {
"bestValue": true,
"index": 1,
"value": "1.0",
},
],
"value": "1.0",
},
Object {
"bestValue": true,
"metric": Object {
"id": "security_review_rating",
"key": "security_review_rating",
"name": "security_review_rating",
"type": "RATING",
},
"periods": undefined,
"value": "1.0",
},
Object {
"bestValue": true,
"leak": "1",
"metric": Object {
"id": "new_security_review_rating",
"key": "new_security_review_rating",
"name": "new_security_review_rating",
"type": "RATING",
},
"periods": Array [
Object {
"bestValue": true,
"index": 1,
"value": "1.0",
},
],
"value": "1.0",
},
Object {
"bestValue": true,
"metric": Object {
@@ -1523,6 +1663,30 @@ exports[`project overview should render correctly 1`] = `
"name": "new_security_hotspots",
"type": "INT",
},
Object {
"id": "security_hotspots_reviewed",
"key": "security_hotspots_reviewed",
"name": "security_hotspots_reviewed",
"type": "INT",
},
Object {
"id": "new_security_hotspots_reviewed",
"key": "new_security_hotspots_reviewed",
"name": "new_security_hotspots_reviewed",
"type": "INT",
},
Object {
"id": "security_review_rating",
"key": "security_review_rating",
"name": "security_review_rating",
"type": "RATING",
},
Object {
"id": "new_security_review_rating",
"key": "new_security_review_rating",
"name": "new_security_review_rating",
"type": "RATING",
},
Object {
"id": "code_smells",
"key": "code_smells",

+ 1010
- 177
server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanel-test.tsx.snap
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 45
- 0
server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/SecurityHotspotsReviewed-test.tsx.snap Просмотреть файл

@@ -0,0 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<Fragment>
<span
className="huge"
>
1.0%
</span>
<span
className="big-spacer-left"
>
overview.measures.security_hotspots_reviewed
</span>
</Fragment>
`;

exports[`should render correctly: no measures 1`] = `
<Fragment>
<span
aria-label="no_data"
className="overview-measures-empty-value"
/>
<span
className="big-spacer-left"
>
overview.measures.security_hotspots_reviewed
</span>
</Fragment>
`;

exports[`should render correctly: on new code 1`] = `
<Fragment>
<span
className="huge"
>
1.0%
</span>
<span
className="big-spacer-left"
>
overview.measures.security_hotspots_reviewed
</span>
</Fragment>
`;

+ 1
- 1
server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx Просмотреть файл

@@ -65,7 +65,7 @@ export function IssueLabel(props: IssueLabelProps) {
className="overview-measures-value text-light"
to={
type === IssueType.SecurityHotspot
? getComponentSecurityHotspotsUrl(component.key, getBranchLikeQuery(branchLike))
? getComponentSecurityHotspotsUrl(component.key, params)
: getComponentIssuesUrl(component.key, params)
}>
{formatMeasure(value, 'SHORT_INT')}

+ 8
- 0
server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueLabel-test.tsx.snap Просмотреть файл

@@ -122,8 +122,12 @@ exports[`should render correctly for hotspots 1`] = `
Object {
"pathname": "/security_hotspots",
"query": Object {
"assignedToMe": undefined,
"branch": undefined,
"hotspots": undefined,
"id": "my-project",
"pullRequest": "1001",
"sinceLeakPeriod": "false",
},
}
}
@@ -151,8 +155,12 @@ exports[`should render correctly for hotspots 2`] = `
Object {
"pathname": "/security_hotspots",
"query": Object {
"assignedToMe": undefined,
"branch": undefined,
"hotspots": undefined,
"id": "my-project",
"pullRequest": "1001",
"sinceLeakPeriod": "true",
},
}
}

+ 1
- 1
server/sonar-web/src/main/js/apps/overview/styles.css Просмотреть файл

@@ -75,7 +75,7 @@
}

.overview-measures-aside {
flex-basis: 180px;
flex-basis: 200px;
box-sizing: border-box;
}


+ 6
- 1
server/sonar-web/src/main/js/apps/overview/utils.ts Просмотреть файл

@@ -43,8 +43,14 @@ export const METRICS: string[] = [
MetricKey.new_vulnerabilities,
MetricKey.security_rating,
MetricKey.new_security_rating,

// hotspots
MetricKey.security_hotspots,
MetricKey.new_security_hotspots,
MetricKey.security_hotspots_reviewed,
MetricKey.new_security_hotspots_reviewed,
MetricKey.security_review_rating,
MetricKey.new_security_review_rating,

// code smells
MetricKey.code_smells,
@@ -169,7 +175,6 @@ const ISSUETYPE_MAP = {
rating: MetricKey.security_review_rating,
newRating: MetricKey.new_security_review_rating,
ratingName: 'SecurityReview',

iconClass: SecurityHotspotIcon
}
};

+ 22
- 0
server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts Просмотреть файл

@@ -20,6 +20,7 @@
import {
getComponentDrilldownUrl,
getComponentIssuesUrl,
getComponentSecurityHotspotsUrl,
getQualityGatesUrl,
getQualityGateUrl
} from '../urls';
@@ -45,6 +46,27 @@ describe('#getComponentIssuesUrl', () => {
});
});

describe('getComponentSecurityHotspotsUrl', () => {
it('should work with no extra parameters', () => {
expect(getComponentSecurityHotspotsUrl(SIMPLE_COMPONENT_KEY, {})).toEqual({
pathname: '/security_hotspots',
query: { id: SIMPLE_COMPONENT_KEY }
});
});

it('should forward some query parameters', () => {
expect(
getComponentSecurityHotspotsUrl(SIMPLE_COMPONENT_KEY, {
sinceLeakPeriod: 'true',
ignoredParam: '1234'
})
).toEqual({
pathname: '/security_hotspots',
query: { id: SIMPLE_COMPONENT_KEY, sinceLeakPeriod: 'true' }
});
});
});

describe('#getComponentDrilldownUrl', () => {
it('should return component drilldown url', () => {
expect(

+ 5
- 1
server/sonar-web/src/main/js/helpers/urls.ts Просмотреть файл

@@ -78,7 +78,11 @@ export function getComponentIssuesUrl(componentKey: string, query?: Query): Loca
* Generate URL for a component's security hotspot page
*/
export function getComponentSecurityHotspotsUrl(componentKey: string, query: Query = {}): Location {
return { pathname: '/security_hotspots', query: { ...query, id: componentKey } };
const { branch, pullRequest, sinceLeakPeriod, hotspots, assignedToMe } = query;
return {
pathname: '/security_hotspots',
query: { id: componentKey, branch, pullRequest, sinceLeakPeriod, hotspots, assignedToMe }
};
}

/**

+ 2
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties Просмотреть файл

@@ -2742,6 +2742,7 @@ overview.recent_activity=Recent Activity
overview.measures=Measures
overview.measures.empty_explanation=Measures on New Code will appear after the second analysis of this branch.
overview.measures.empty_link={learn_more_link} about the Clean as You Code approach.
overview.measures.security_hotspots_reviewed=Reviewed

overview.project.no_lines_of_code=This project has no lines of code.
overview.project.empty=This project is empty.
@@ -2750,6 +2751,7 @@ overview.project.branch_X_empty=The "{0}" branch of this project is empty.
overview.project.main_branch_no_lines_of_code=The main branch has no lines of code.
overview.project.main_branch_empty=The main branch of this project is empty.
overview.project.branch_needs_new_analysis=The branch data is incomplete. Run a new analysis to update it.

overview.coverage_on=Coverage on
overview.coverage_on_X_lines=Coverage on {count} Lines to cover
overview.coverage_on_X_new_lines=Coverage on {count} New Lines to cover

Загрузка…
Отмена
Сохранить