Browse Source

SONAR-12981 Add security review rating and hotspots in PR overview.

tags/8.2.0.32929
Mathieu Suen 4 years ago
parent
commit
0a1d327928
17 changed files with 797 additions and 573 deletions
  1. 6
    2
      server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx
  2. 27
    15
      server/sonar-web/src/main/js/apps/overview/components/IssueRating.tsx
  3. 2
    2
      server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueRating-test.tsx
  4. 2
    8
      server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueLabel-test.tsx.snap
  5. 15
    0
      server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueRating-test.tsx.snap
  6. 26
    37
      server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx
  7. 240
    6
      server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/PullRequestOverview-test.tsx.snap
  8. 5
    3
      server/sonar-web/src/main/js/apps/overview/utils.ts
  9. 30
    73
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx
  10. 31
    73
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx
  11. 59
    0
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardRatingMeasure.tsx
  12. 47
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardRatingMeasure-test.tsx
  13. 150
    234
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap
  14. 81
    117
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap
  15. 69
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardRatingMeasure-test.tsx.snap
  16. 2
    2
      server/sonar-web/src/main/js/helpers/urls.ts
  17. 5
    1
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 6
- 2
server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx View File

@@ -25,7 +25,7 @@ import DocTooltip from '../../../components/docs/DocTooltip';
import { getLeakValue } from '../../../components/measure/utils';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { findMeasure } from '../../../helpers/measures';
import { getComponentIssuesUrl } from '../../../helpers/urls';
import { getComponentIssuesUrl, getComponentSecurityHotspotsUrl } from '../../../helpers/urls';
import { BranchLike } from '../../../types/branch-like';
import { getIssueIconClass, getIssueMetricKey, IssueType } from '../utils';

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

+ 27
- 15
server/sonar-web/src/main/js/apps/overview/components/IssueRating.tsx View File

@@ -34,32 +34,44 @@ export interface IssueRatingProps {
useDiffMetric?: boolean;
}

export function IssueRating(props: IssueRatingProps) {
const { branchLike, component, measures, type, useDiffMetric = false } = props;
function renderRatingLink(props: IssueRatingProps) {
const { branchLike, component, useDiffMetric = false, measures, type } = props;
const rating = getIssueRatingMetricKey(type, useDiffMetric);
const measure = findMeasure(measures, rating);

if (!rating || !measure) {
return null;
return (
<div className="padded">
<Rating value={undefined} />
</div>
);
}

const value = useDiffMetric ? getLeakValue(measure) : measure.value;
const value = measure && (useDiffMetric ? getLeakValue(measure) : measure.value);
const tooltip = value && getRatingTooltip(rating, Number(value));

return (
<Tooltip overlay={tooltip}>
<span>
<DrilldownLink
branchLike={branchLike}
className="link-no-underline"
component={component.key}
metric={rating}>
<Rating value={value} />
</DrilldownLink>
</span>
</Tooltip>
);
}

export function IssueRating(props: IssueRatingProps) {
const { type } = props;

return (
<>
<span className="flex-1 big-spacer-right text-right">{getIssueRatingName(type)}</span>
<Tooltip overlay={tooltip}>
<span>
<DrilldownLink
branchLike={branchLike}
className="link-no-underline"
component={component.key}
metric={rating}>
<Rating value={value} />
</DrilldownLink>
</span>
</Tooltip>
{renderRatingLink(props)}
</>
);
}

+ 2
- 2
server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueRating-test.tsx View File

@@ -44,8 +44,8 @@ it('should render correctly if no values are present', () => {
expect(
shallowRender({
measures: [mockMeasureEnhanced({ metric: mockMetric({ key: 'NONE' }) })]
}).type()
).toBeNull();
})
).toMatchSnapshot();
});

function shallowRender(props: Partial<IssueRatingProps> = {}) {

+ 2
- 8
server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueLabel-test.tsx.snap View File

@@ -120,13 +120,10 @@ exports[`should render correctly for hotspots 1`] = `
style={Object {}}
to={
Object {
"pathname": "/project/issues",
"pathname": "/security_hotspots",
"query": Object {
"id": "my-project",
"pullRequest": "1001",
"resolved": "false",
"sinceLeakPeriod": "false",
"types": "SECURITY_HOTSPOT",
},
}
}
@@ -152,13 +149,10 @@ exports[`should render correctly for hotspots 2`] = `
style={Object {}}
to={
Object {
"pathname": "/project/issues",
"pathname": "/security_hotspots",
"query": Object {
"id": "my-project",
"pullRequest": "1001",
"resolved": "false",
"sinceLeakPeriod": "true",
"types": "SECURITY_HOTSPOT",
},
}
}

+ 15
- 0
server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueRating-test.tsx.snap View File

@@ -209,3 +209,18 @@ exports[`should render correctly for vulnerabilities 2`] = `
</Tooltip>
</Fragment>
`;

exports[`should render correctly if no values are present 1`] = `
<Fragment>
<span
className="flex-1 big-spacer-right text-right"
>
metric_domain.Reliability
</span>
<div
className="padded"
>
<Rating />
</div>
</Fragment>
`;

+ 26
- 37
server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx View File

@@ -175,44 +175,33 @@ export class PullRequestOverview extends React.PureComponent<Props, State> {
</h2>

<div className="overview-panel-content">
{[IssueType.Bug, IssueType.Vulnerability, IssueType.CodeSmell].map(
(type: IssueType) => (
<div className="overview-measures-row display-flex-row" key={type}>
<div className="overview-panel-big-padded flex-1 small display-flex-center">
<IssueLabel
branchLike={branchLike}
component={component}
measures={measures}
type={type}
useDiffMetric={true}
/>
</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={true}
/>
</div>
)}
<div className="overview-panel-big-padded overview-measures-aside display-flex-center">
<IssueRating
branchLike={branchLike}
component={component}
measures={measures}
type={type}
useDiffMetric={true}
/>
</div>
{[
IssueType.Bug,
IssueType.Vulnerability,
IssueType.SecurityHotspot,
IssueType.CodeSmell
].map((type: IssueType) => (
<div className="overview-measures-row display-flex-row" key={type}>
<div className="overview-panel-big-padded flex-1 small display-flex-center">
<IssueLabel
branchLike={branchLike}
component={component}
measures={measures}
type={type}
useDiffMetric={true}
/>
</div>
)
)}
<div className="overview-panel-big-padded overview-measures-aside display-flex-center">
<IssueRating
branchLike={branchLike}
component={component}
measures={measures}
type={type}
useDiffMetric={true}
/>
</div>
</div>
))}

{[MeasurementType.Coverage, MeasurementType.Duplication].map(
(type: MeasurementType) => (

+ 240
- 6
server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/PullRequestOverview-test.tsx.snap View File

@@ -483,7 +483,125 @@ exports[`should render correctly for a failed QG 1`] = `
/>
</div>
<div
className="flex-1 small display-flex-center"
className="overview-panel-big-padded overview-measures-aside display-flex-center"
>
<Memo(IssueRating)
branchLike={
Object {
"analysisDate": "2018-01-01",
"base": "master",
"branch": "feature/foo/bar",
"key": "1001",
"target": "master",
"title": "Foo Bar feature",
}
}
component={
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 [],
}
}
measures={
Array [
Object {
"bestValue": true,
"metric": Object {
"id": "new_bugs",
"key": "new_bugs",
"name": "new_bugs",
"type": "PERCENT",
},
"periods": Array [
Object {
"bestValue": true,
"index": 1,
"value": "1.0",
},
],
"value": "1.0",
},
Object {
"bestValue": true,
"metric": Object {
"id": "new_vulnerabilities",
"key": "new_vulnerabilities",
"name": "new_vulnerabilities",
"type": "PERCENT",
},
"periods": Array [
Object {
"bestValue": true,
"index": 1,
"value": "1.0",
},
],
"value": "1.0",
},
Object {
"bestValue": true,
"metric": Object {
"id": "new_code_smells",
"key": "new_code_smells",
"name": "new_code_smells",
"type": "PERCENT",
},
"periods": Array [
Object {
"bestValue": true,
"index": 1,
"value": "1.0",
},
],
"value": "1.0",
},
Object {
"bestValue": true,
"metric": Object {
"id": "new_security_hotspots",
"key": "new_security_hotspots",
"name": "new_security_hotspots",
"type": "PERCENT",
},
"periods": Array [
Object {
"bestValue": true,
"index": 1,
"value": "1.0",
},
],
"value": "1.0",
},
]
}
type="VULNERABILITY"
useDiffMetric={true}
/>
</div>
</div>
<div
className="overview-measures-row display-flex-row"
key="SECURITY_HOTSPOT"
>
<div
className="overview-panel-big-padded flex-1 small display-flex-center"
>
<Memo(IssueLabel)
branchLike={
@@ -519,7 +637,6 @@ exports[`should render correctly for a failed QG 1`] = `
"tags": Array [],
}
}
docTooltip={Promise {}}
measures={
Array [
Object {
@@ -705,7 +822,7 @@ exports[`should render correctly for a failed QG 1`] = `
},
]
}
type="VULNERABILITY"
type="SECURITY_HOTSPOT"
useDiffMetric={true}
/>
</div>
@@ -1745,7 +1862,125 @@ exports[`should render correctly for a passed QG 1`] = `
/>
</div>
<div
className="flex-1 small display-flex-center"
className="overview-panel-big-padded overview-measures-aside display-flex-center"
>
<Memo(IssueRating)
branchLike={
Object {
"analysisDate": "2018-01-01",
"base": "master",
"branch": "feature/foo/bar",
"key": "1001",
"target": "master",
"title": "Foo Bar feature",
}
}
component={
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 [],
}
}
measures={
Array [
Object {
"bestValue": true,
"metric": Object {
"id": "new_bugs",
"key": "new_bugs",
"name": "new_bugs",
"type": "PERCENT",
},
"periods": Array [
Object {
"bestValue": true,
"index": 1,
"value": "1.0",
},
],
"value": "1.0",
},
Object {
"bestValue": true,
"metric": Object {
"id": "new_vulnerabilities",
"key": "new_vulnerabilities",
"name": "new_vulnerabilities",
"type": "PERCENT",
},
"periods": Array [
Object {
"bestValue": true,
"index": 1,
"value": "1.0",
},
],
"value": "1.0",
},
Object {
"bestValue": true,
"metric": Object {
"id": "new_code_smells",
"key": "new_code_smells",
"name": "new_code_smells",
"type": "PERCENT",
},
"periods": Array [
Object {
"bestValue": true,
"index": 1,
"value": "1.0",
},
],
"value": "1.0",
},
Object {
"bestValue": true,
"metric": Object {
"id": "new_security_hotspots",
"key": "new_security_hotspots",
"name": "new_security_hotspots",
"type": "PERCENT",
},
"periods": Array [
Object {
"bestValue": true,
"index": 1,
"value": "1.0",
},
],
"value": "1.0",
},
]
}
type="VULNERABILITY"
useDiffMetric={true}
/>
</div>
</div>
<div
className="overview-measures-row display-flex-row"
key="SECURITY_HOTSPOT"
>
<div
className="overview-panel-big-padded flex-1 small display-flex-center"
>
<Memo(IssueLabel)
branchLike={
@@ -1781,7 +2016,6 @@ exports[`should render correctly for a passed QG 1`] = `
"tags": Array [],
}
}
docTooltip={Promise {}}
measures={
Array [
Object {
@@ -1967,7 +2201,7 @@ exports[`should render correctly for a passed QG 1`] = `
},
]
}
type="VULNERABILITY"
type="SECURITY_HOTSPOT"
useDiffMetric={true}
/>
</div>

+ 5
- 3
server/sonar-web/src/main/js/apps/overview/utils.ts View File

@@ -88,6 +88,7 @@ export const PR_METRICS: string[] = [
MetricKey.new_reliability_rating,
MetricKey.new_vulnerabilities,
MetricKey.new_security_hotspots,
MetricKey.new_security_review_rating,
MetricKey.new_security_rating
];

@@ -165,9 +166,10 @@ const ISSUETYPE_MAP = {
[IssueType.SecurityHotspot]: {
metric: MetricKey.security_hotspots,
newMetric: MetricKey.new_security_hotspots,
rating: '',
newRating: '',
ratingName: '',
rating: MetricKey.security_review_rating,
newRating: MetricKey.new_security_review_rating,
ratingName: 'SecurityReview',

iconClass: SecurityHotspotIcon
}
};

+ 30
- 73
server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx View File

@@ -18,13 +18,9 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import BugIcon from 'sonar-ui-common/components/icons/BugIcon';
import CodeSmellIcon from 'sonar-ui-common/components/icons/CodeSmellIcon';
import SecurityHotspotIcon from 'sonar-ui-common/components/icons/SecurityHotspotIcon';
import VulnerabilityIcon from 'sonar-ui-common/components/icons/VulnerabilityIcon';
import Rating from 'sonar-ui-common/components/ui/Rating';
import { translate } from 'sonar-ui-common/helpers/l10n';
import Measure from '../../../components/measure/Measure';
import ProjectCardRatingMeasure from './ProjectCardRatingMeasure';

interface Props {
measures: T.Dict<string>;
@@ -33,77 +29,38 @@ interface Props {
export default function ProjectCardLeakMeasures({ measures }: Props) {
return (
<div className="project-card-leak-measures">
<div className="project-card-measure" data-key="new_reliability_rating">
<div className="project-card-measure-inner">
<div className="project-card-measure-number">
<Measure
className="spacer-right"
metricKey="new_bugs"
metricType="SHORT_INT"
value={measures['new_bugs']}
/>
<Rating value={measures['new_reliability_rating']} />
</div>
<div className="project-card-measure-label-with-icon">
<BugIcon className="little-spacer-right text-bottom" />
{translate('metric.bugs.name')}
</div>
</div>
</div>
<ProjectCardRatingMeasure
iconLabel={translate('metric.bugs.name')}
measures={measures}
metricKey="new_bugs"
metricRatingKey="new_reliability_rating"
metricType="SHORT_INT"
/>

<div className="project-card-measure" data-key="new_security_rating">
<div className="project-card-measure-inner">
<div className="project-card-measure-number">
<Measure
className="spacer-right"
metricKey="new_vulnerabilities"
metricType="SHORT_INT"
value={measures['new_vulnerabilities']}
/>
<Rating value={measures['new_security_rating']} />
</div>
<div className="project-card-measure-label-with-icon">
<VulnerabilityIcon className="little-spacer-right text-bottom" />
{translate('metric.vulnerabilities.name')}
</div>
</div>
</div>
<ProjectCardRatingMeasure
iconLabel={translate('metric.vulnerabilities.name')}
measures={measures}
metricKey="new_vulnerabilities"
metricRatingKey="new_security_rating"
metricType="SHORT_INT"
/>

<div className="project-card-measure" data-key="new_security_review_rating">
<div className="project-card-measure-inner">
<div className="project-card-measure-number">
<Measure
className="spacer-right"
metricKey="new_security_hotspots_reviewed"
metricType="PERCENT"
value={measures['new_security_hotspots_reviewed']}
/>
<Rating value={measures['new_security_review_rating']} />
</div>
<div className="project-card-measure-label-with-icon">
<SecurityHotspotIcon className="little-spacer-right text-bottom" />
{translate('metric.security_hotspots_reviewed.extra_short_name')}
</div>
</div>
</div>
<ProjectCardRatingMeasure
iconKey="security_hotspots"
iconLabel={translate('projects.security_hotspots_reviewed')}
measures={measures}
metricKey="new_security_hotspots_reviewed"
metricRatingKey="new_security_review_rating"
metricType="PERCENT"
/>

<div className="project-card-measure" data-key="new_maintainability_rating">
<div className="project-card-measure-inner">
<div className="project-card-measure-number">
<Measure
className="spacer-right"
metricKey="new_code_smells"
metricType="SHORT_INT"
value={measures['new_code_smells']}
/>
<Rating value={measures['new_maintainability_rating']} />
</div>
<div className="project-card-measure-label-with-icon">
<CodeSmellIcon className="little-spacer-right text-bottom" />
{translate('metric.code_smells.name')}
</div>
</div>
</div>
<ProjectCardRatingMeasure
iconLabel={translate('metric.code_smells.name')}
measures={measures}
metricKey="new_code_smells"
metricRatingKey="new_maintainability_rating"
metricType="SHORT_INT"
/>

{measures['new_coverage'] != null && (
<div className="project-card-measure" data-key="new_coverage">

+ 31
- 73
server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx View File

@@ -18,17 +18,13 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import BugIcon from 'sonar-ui-common/components/icons/BugIcon';
import CodeSmellIcon from 'sonar-ui-common/components/icons/CodeSmellIcon';
import SecurityHotspotIcon from 'sonar-ui-common/components/icons/SecurityHotspotIcon';
import VulnerabilityIcon from 'sonar-ui-common/components/icons/VulnerabilityIcon';
import DuplicationsRating from 'sonar-ui-common/components/ui/DuplicationsRating';
import Rating from 'sonar-ui-common/components/ui/Rating';
import SizeRating from 'sonar-ui-common/components/ui/SizeRating';
import { translate } from 'sonar-ui-common/helpers/l10n';
import Measure from '../../../components/measure/Measure';
import CoverageRating from '../../../components/ui/CoverageRating';
import ProjectCardLanguagesContainer from './ProjectCardLanguagesContainer';
import ProjectCardRatingMeasure from './ProjectCardRatingMeasure';

interface Props {
measures: T.Dict<string | undefined>;
@@ -46,77 +42,39 @@ export default function ProjectCardOverallMeasures({ measures }: Props) {

return (
<div className="project-card-measures">
<div className="project-card-measure" data-key="reliability_rating">
<div className="project-card-measure-inner">
<div className="project-card-measure-number">
<Measure
className="spacer-right"
metricKey="bugs"
metricType="SHORT_INT"
value={measures['bugs']}
/>
<Rating value={measures['reliability_rating']} />
</div>
<div className="project-card-measure-label-with-icon">
<BugIcon className="little-spacer-right text-bottom" />
{translate('metric.bugs.name')}
</div>
</div>
</div>
<ProjectCardRatingMeasure
iconLabel={translate('metric.bugs.name')}
measures={measures}
metricKey="bugs"
metricRatingKey="reliability_rating"
metricType="SHORT_INT"
/>

<div className="project-card-measure" data-key="security_rating">
<div className="project-card-measure-inner">
<div className="project-card-measure-number">
<Measure
className="spacer-right"
metricKey="vulnerabilities"
metricType="SHORT_INT"
value={measures['vulnerabilities']}
/>
<Rating value={measures['security_rating']} />
</div>
<div className="project-card-measure-label-with-icon">
<VulnerabilityIcon className="little-spacer-right text-bottom" />
{translate('metric.vulnerabilities.name')}
</div>
</div>
</div>
<ProjectCardRatingMeasure
iconLabel={translate('metric.vulnerabilities.name')}
measures={measures}
metricKey="vulnerabilities"
metricRatingKey="security_rating"
metricType="SHORT_INT"
/>

<div className="project-card-measure" data-key="security_review_rating">
<div className="project-card-measure-inner">
<div className="project-card-measure-number">
<Measure
className="spacer-right"
metricKey="security_hotspots_reviewed"
metricType="PERCENT"
value={measures['security_hotspots_reviewed']}
/>
<Rating value={measures['security_review_rating']} />
</div>
<div className="project-card-measure-label-with-icon">
<SecurityHotspotIcon className="little-spacer-right text-bottom" />
{translate('metric.security_hotspots_reviewed.extra_short_name')}
</div>
</div>
</div>
<ProjectCardRatingMeasure
iconKey="security_hotspots"
iconLabel={translate('projects.security_hotspots_reviewed')}
measures={measures}
metricKey="security_hotspots_reviewed"
metricRatingKey="security_review_rating"
metricType="PERCENT"
/>

<ProjectCardRatingMeasure
iconLabel={translate('metric.code_smells.name')}
measures={measures}
metricKey="code_smells"
metricRatingKey="sqale_rating"
metricType="SHORT_INT"
/>

<div className="project-card-measure" data-key="sqale_rating">
<div className="project-card-measure-inner">
<div className="project-card-measure-number">
<Measure
className="spacer-right"
metricKey="code_smells"
metricType="SHORT_INT"
value={measures['code_smells']}
/>
<Rating value={measures['sqale_rating']} />
</div>
<div className="project-card-measure-label-with-icon">
<CodeSmellIcon className="little-spacer-right text-bottom" />
{translate('metric.code_smells.name')}
</div>
</div>
</div>
{measures['coverage'] != null && (
<div className="project-card-measure" data-key="coverage">
<div className="project-card-measure-inner">

+ 59
- 0
server/sonar-web/src/main/js/apps/projects/components/ProjectCardRatingMeasure.tsx View File

@@ -0,0 +1,59 @@
/*
* 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 IssueTypeIcon from 'sonar-ui-common/components/icons/IssueTypeIcon';
import Rating from 'sonar-ui-common/components/ui/Rating';
import Measure from '../../../components/measure/Measure';

export interface ProjectCardRatingMeasureProps {
iconKey?: string;
iconLabel: string;
measures: T.Dict<string | undefined>;
metricKey: string;
metricRatingKey: string;
metricType: string;
}

export default function ProjectCardRatingMeasure(props: ProjectCardRatingMeasureProps) {
const { iconKey, iconLabel, measures, metricKey, metricRatingKey, metricType } = props;

return (
<div className="project-card-measure" data-key={metricRatingKey}>
<div className="project-card-measure-inner">
<div className="project-card-measure-number">
<Measure
className="spacer-right"
metricKey={metricKey}
metricType={metricType}
value={measures[metricKey]}
/>

<Rating value={measures[metricRatingKey]} />
</div>

<div className="project-card-measure-label-with-icon">
<IssueTypeIcon className="little-spacer-right text-bottom" query={iconKey || metricKey} />

{iconLabel}
</div>
</div>
</div>
);
}

+ 47
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardRatingMeasure-test.tsx View File

@@ -0,0 +1,47 @@
/*
* 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 ProjectCardRatingMeasure, {
ProjectCardRatingMeasureProps
} from '../ProjectCardRatingMeasure';

it('renders', () => {
expect(shallowRender()).toMatchSnapshot();
expect(shallowRender({ iconKey: 'overriddenIconKey' })).toMatchSnapshot('iconKey');
});

function shallowRender(overrides: Partial<ProjectCardRatingMeasureProps> = {}) {
const measures = {
security_rating: '1',
vulnerabilities: '0',
dummyThatShouldBeIgnored: 'yes'
};
return shallow(
<ProjectCardRatingMeasure
iconLabel="label"
measures={measures}
metricKey="vulnerabilities"
metricRatingKey="security_rating"
metricType="SHORT_INT"
{...overrides}
/>
);
}

+ 150
- 234
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap View File

@@ -4,123 +4,87 @@ exports[`should render correctly with all data 1`] = `
<div
className="project-card-leak-measures"
>
<div
className="project-card-measure"
data-key="new_reliability_rating"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<Measure
className="spacer-right"
metricKey="new_bugs"
metricType="SHORT_INT"
value="8"
/>
<Rating
value="1.0"
/>
</div>
<div
className="project-card-measure-label-with-icon"
>
<BugIcon
className="little-spacer-right text-bottom"
/>
metric.bugs.name
</div>
</div>
</div>
<div
className="project-card-measure"
data-key="new_security_rating"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<Measure
className="spacer-right"
metricKey="new_vulnerabilities"
metricType="SHORT_INT"
value="2"
/>
<Rating
value="2.0"
/>
</div>
<div
className="project-card-measure-label-with-icon"
>
<VulnerabilityIcon
className="little-spacer-right text-bottom"
/>
metric.vulnerabilities.name
</div>
</div>
</div>
<div
className="project-card-measure"
data-key="new_security_review_rating"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<Measure
className="spacer-right"
metricKey="new_security_hotspots_reviewed"
metricType="PERCENT"
/>
<Rating />
</div>
<div
className="project-card-measure-label-with-icon"
>
<SecurityHotspotIcon
className="little-spacer-right text-bottom"
/>
metric.security_hotspots_reviewed.extra_short_name
</div>
</div>
</div>
<div
className="project-card-measure"
data-key="new_maintainability_rating"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<Measure
className="spacer-right"
metricKey="new_code_smells"
metricType="SHORT_INT"
value="0"
/>
<Rating
value="1.0"
/>
</div>
<div
className="project-card-measure-label-with-icon"
>
<CodeSmellIcon
className="little-spacer-right text-bottom"
/>
metric.code_smells.name
</div>
</div>
</div>
<ProjectCardRatingMeasure
iconLabel="metric.bugs.name"
measures={
Object {
"alert_status": "ERROR",
"new_bugs": "8",
"new_code_smells": "0",
"new_coverage": "26.55",
"new_duplicated_lines_density": "0.55",
"new_lines": "87",
"new_maintainability_rating": "1.0",
"new_reliability_rating": "1.0",
"new_security_rating": "2.0",
"new_vulnerabilities": "2",
}
}
metricKey="new_bugs"
metricRatingKey="new_reliability_rating"
metricType="SHORT_INT"
/>
<ProjectCardRatingMeasure
iconLabel="metric.vulnerabilities.name"
measures={
Object {
"alert_status": "ERROR",
"new_bugs": "8",
"new_code_smells": "0",
"new_coverage": "26.55",
"new_duplicated_lines_density": "0.55",
"new_lines": "87",
"new_maintainability_rating": "1.0",
"new_reliability_rating": "1.0",
"new_security_rating": "2.0",
"new_vulnerabilities": "2",
}
}
metricKey="new_vulnerabilities"
metricRatingKey="new_security_rating"
metricType="SHORT_INT"
/>
<ProjectCardRatingMeasure
iconKey="security_hotspots"
iconLabel="projects.security_hotspots_reviewed"
measures={
Object {
"alert_status": "ERROR",
"new_bugs": "8",
"new_code_smells": "0",
"new_coverage": "26.55",
"new_duplicated_lines_density": "0.55",
"new_lines": "87",
"new_maintainability_rating": "1.0",
"new_reliability_rating": "1.0",
"new_security_rating": "2.0",
"new_vulnerabilities": "2",
}
}
metricKey="new_security_hotspots_reviewed"
metricRatingKey="new_security_review_rating"
metricType="PERCENT"
/>
<ProjectCardRatingMeasure
iconLabel="metric.code_smells.name"
measures={
Object {
"alert_status": "ERROR",
"new_bugs": "8",
"new_code_smells": "0",
"new_coverage": "26.55",
"new_duplicated_lines_density": "0.55",
"new_lines": "87",
"new_maintainability_rating": "1.0",
"new_reliability_rating": "1.0",
"new_security_rating": "2.0",
"new_vulnerabilities": "2",
}
}
metricKey="new_code_smells"
metricRatingKey="new_maintainability_rating"
metricType="SHORT_INT"
/>
<div
className="project-card-measure"
data-key="new_coverage"
@@ -197,123 +161,75 @@ exports[`should render no data style new coverage, new duplications and new line
<div
className="project-card-leak-measures"
>
<div
className="project-card-measure"
data-key="new_reliability_rating"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<Measure
className="spacer-right"
metricKey="new_bugs"
metricType="SHORT_INT"
value="8"
/>
<Rating
value="1.0"
/>
</div>
<div
className="project-card-measure-label-with-icon"
>
<BugIcon
className="little-spacer-right text-bottom"
/>
metric.bugs.name
</div>
</div>
</div>
<div
className="project-card-measure"
data-key="new_security_rating"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<Measure
className="spacer-right"
metricKey="new_vulnerabilities"
metricType="SHORT_INT"
value="2"
/>
<Rating
value="2.0"
/>
</div>
<div
className="project-card-measure-label-with-icon"
>
<VulnerabilityIcon
className="little-spacer-right text-bottom"
/>
metric.vulnerabilities.name
</div>
</div>
</div>
<div
className="project-card-measure"
data-key="new_security_review_rating"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<Measure
className="spacer-right"
metricKey="new_security_hotspots_reviewed"
metricType="PERCENT"
/>
<Rating />
</div>
<div
className="project-card-measure-label-with-icon"
>
<SecurityHotspotIcon
className="little-spacer-right text-bottom"
/>
metric.security_hotspots_reviewed.extra_short_name
</div>
</div>
</div>
<div
className="project-card-measure"
data-key="new_maintainability_rating"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<Measure
className="spacer-right"
metricKey="new_code_smells"
metricType="SHORT_INT"
value="0"
/>
<Rating
value="1.0"
/>
</div>
<div
className="project-card-measure-label-with-icon"
>
<CodeSmellIcon
className="little-spacer-right text-bottom"
/>
metric.code_smells.name
</div>
</div>
</div>
<ProjectCardRatingMeasure
iconLabel="metric.bugs.name"
measures={
Object {
"alert_status": "ERROR",
"new_bugs": "8",
"new_code_smells": "0",
"new_maintainability_rating": "1.0",
"new_reliability_rating": "1.0",
"new_security_rating": "2.0",
"new_vulnerabilities": "2",
}
}
metricKey="new_bugs"
metricRatingKey="new_reliability_rating"
metricType="SHORT_INT"
/>
<ProjectCardRatingMeasure
iconLabel="metric.vulnerabilities.name"
measures={
Object {
"alert_status": "ERROR",
"new_bugs": "8",
"new_code_smells": "0",
"new_maintainability_rating": "1.0",
"new_reliability_rating": "1.0",
"new_security_rating": "2.0",
"new_vulnerabilities": "2",
}
}
metricKey="new_vulnerabilities"
metricRatingKey="new_security_rating"
metricType="SHORT_INT"
/>
<ProjectCardRatingMeasure
iconKey="security_hotspots"
iconLabel="projects.security_hotspots_reviewed"
measures={
Object {
"alert_status": "ERROR",
"new_bugs": "8",
"new_code_smells": "0",
"new_maintainability_rating": "1.0",
"new_reliability_rating": "1.0",
"new_security_rating": "2.0",
"new_vulnerabilities": "2",
}
}
metricKey="new_security_hotspots_reviewed"
metricRatingKey="new_security_review_rating"
metricType="PERCENT"
/>
<ProjectCardRatingMeasure
iconLabel="metric.code_smells.name"
measures={
Object {
"alert_status": "ERROR",
"new_bugs": "8",
"new_code_smells": "0",
"new_maintainability_rating": "1.0",
"new_reliability_rating": "1.0",
"new_security_rating": "2.0",
"new_vulnerabilities": "2",
}
}
metricKey="new_code_smells"
metricRatingKey="new_maintainability_rating"
metricType="SHORT_INT"
/>
<div
className="project-card-measure"
data-key="new_duplicated_lines_density"

+ 81
- 117
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap View File

@@ -4,123 +4,87 @@ exports[`should render correctly with all data 1`] = `
<div
className="project-card-measures"
>
<div
className="project-card-measure"
data-key="reliability_rating"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<Measure
className="spacer-right"
metricKey="bugs"
metricType="SHORT_INT"
value="17"
/>
<Rating
value="1.0"
/>
</div>
<div
className="project-card-measure-label-with-icon"
>
<BugIcon
className="little-spacer-right text-bottom"
/>
metric.bugs.name
</div>
</div>
</div>
<div
className="project-card-measure"
data-key="security_rating"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<Measure
className="spacer-right"
metricKey="vulnerabilities"
metricType="SHORT_INT"
value="0"
/>
<Rating
value="1.0"
/>
</div>
<div
className="project-card-measure-label-with-icon"
>
<VulnerabilityIcon
className="little-spacer-right text-bottom"
/>
metric.vulnerabilities.name
</div>
</div>
</div>
<div
className="project-card-measure"
data-key="security_review_rating"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<Measure
className="spacer-right"
metricKey="security_hotspots_reviewed"
metricType="PERCENT"
/>
<Rating />
</div>
<div
className="project-card-measure-label-with-icon"
>
<SecurityHotspotIcon
className="little-spacer-right text-bottom"
/>
metric.security_hotspots_reviewed.extra_short_name
</div>
</div>
</div>
<div
className="project-card-measure"
data-key="sqale_rating"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<Measure
className="spacer-right"
metricKey="code_smells"
metricType="SHORT_INT"
value="132"
/>
<Rating
value="1.0"
/>
</div>
<div
className="project-card-measure-label-with-icon"
>
<CodeSmellIcon
className="little-spacer-right text-bottom"
/>
metric.code_smells.name
</div>
</div>
</div>
<ProjectCardRatingMeasure
iconLabel="metric.bugs.name"
measures={
Object {
"alert_status": "ERROR",
"bugs": "17",
"code_smells": "132",
"coverage": "88.3",
"duplicated_lines_density": "9.8",
"ncloc": "2053",
"reliability_rating": "1.0",
"security_rating": "1.0",
"sqale_rating": "1.0",
"vulnerabilities": "0",
}
}
metricKey="bugs"
metricRatingKey="reliability_rating"
metricType="SHORT_INT"
/>
<ProjectCardRatingMeasure
iconLabel="metric.vulnerabilities.name"
measures={
Object {
"alert_status": "ERROR",
"bugs": "17",
"code_smells": "132",
"coverage": "88.3",
"duplicated_lines_density": "9.8",
"ncloc": "2053",
"reliability_rating": "1.0",
"security_rating": "1.0",
"sqale_rating": "1.0",
"vulnerabilities": "0",
}
}
metricKey="vulnerabilities"
metricRatingKey="security_rating"
metricType="SHORT_INT"
/>
<ProjectCardRatingMeasure
iconKey="security_hotspots"
iconLabel="projects.security_hotspots_reviewed"
measures={
Object {
"alert_status": "ERROR",
"bugs": "17",
"code_smells": "132",
"coverage": "88.3",
"duplicated_lines_density": "9.8",
"ncloc": "2053",
"reliability_rating": "1.0",
"security_rating": "1.0",
"sqale_rating": "1.0",
"vulnerabilities": "0",
}
}
metricKey="security_hotspots_reviewed"
metricRatingKey="security_review_rating"
metricType="PERCENT"
/>
<ProjectCardRatingMeasure
iconLabel="metric.code_smells.name"
measures={
Object {
"alert_status": "ERROR",
"bugs": "17",
"code_smells": "132",
"coverage": "88.3",
"duplicated_lines_density": "9.8",
"ncloc": "2053",
"reliability_rating": "1.0",
"security_rating": "1.0",
"sqale_rating": "1.0",
"vulnerabilities": "0",
}
}
metricKey="code_smells"
metricRatingKey="sqale_rating"
metricType="SHORT_INT"
/>
<div
className="project-card-measure"
data-key="coverage"

+ 69
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardRatingMeasure-test.tsx.snap View File

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

exports[`renders 1`] = `
<div
className="project-card-measure"
data-key="security_rating"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<Measure
className="spacer-right"
metricKey="vulnerabilities"
metricType="SHORT_INT"
value="0"
/>
<Rating
value="1"
/>
</div>
<div
className="project-card-measure-label-with-icon"
>
<IssueTypeIcon
className="little-spacer-right text-bottom"
query="vulnerabilities"
/>
label
</div>
</div>
</div>
`;

exports[`renders: iconKey 1`] = `
<div
className="project-card-measure"
data-key="security_rating"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<Measure
className="spacer-right"
metricKey="vulnerabilities"
metricType="SHORT_INT"
value="0"
/>
<Rating
value="1"
/>
</div>
<div
className="project-card-measure-label-with-icon"
>
<IssueTypeIcon
className="little-spacer-right text-bottom"
query="overriddenIconKey"
/>
label
</div>
</div>
</div>
`;

+ 2
- 2
server/sonar-web/src/main/js/helpers/urls.ts View File

@@ -77,8 +77,8 @@ export function getComponentIssuesUrl(componentKey: string, query?: Query): Loca
/**
* Generate URL for a component's security hotspot page
*/
export function getComponentSecurityHotspotsUrl(componentKey: string): Location {
return { pathname: '/security_hotspots', query: { id: componentKey } };
export function getComponentSecurityHotspotsUrl(componentKey: string, query: Query = {}): Location {
return { pathname: '/security_hotspots', query: { ...query, id: componentKey } };
}

/**

+ 5
- 1
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -968,6 +968,7 @@ projects.sort.-duplications=by duplications (worst first)
projects.sort.size=by size (smallest first)
projects.sort.-size=by size (biggest first)

projects.security_hotspots_reviewed=Hotspots Reviewed

#------------------------------------------------------------------------------
#
@@ -2223,7 +2224,10 @@ metric.security_hotspots.description=Security Hotspots
metric.security_hotspots.name=Security Hotspots
metric.security_hotspots_reviewed.description=Percentage of Security Hotspots Reviewed
metric.security_hotspots_reviewed.name=Security Hotspots Reviewed
metric.security_hotspots_reviewed.extra_short_name=Hotspots Reviewed
metric.security_hotspots_reviewed_status.description=Security Review Reviewed Status
metric.security_hotspots_reviewed_status.name=Security Review Reviewed Status
metric.security_hotspots_to_review_status.description=Security Review To Review Status
metric.security_hotspots_to_review_status.name=Security Review To Review Status
metric.security_rating.description=Security rating
metric.security_rating.name=Security Rating
metric.security_rating.extra_short_name=Rating

Loading…
Cancel
Save