diff options
author | Mathieu Suen <mathieu.suen@sonarsource.com> | 2020-01-31 15:15:36 +0100 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2020-02-11 20:46:11 +0100 |
commit | 0a1d327928f35e177f3190d810105eb7e661ebc7 (patch) | |
tree | 6ab73d722ed8f77542603a390f4e7f0dc18bf8c8 | |
parent | ec64094599252553e55efa348516ff36477bce57 (diff) | |
download | sonarqube-0a1d327928f35e177f3190d810105eb7e661ebc7.tar.gz sonarqube-0a1d327928f35e177f3190d810105eb7e661ebc7.zip |
SONAR-12981 Add security review rating and hotspots in PR overview.
17 files changed, 797 insertions, 573 deletions
diff --git a/server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx b/server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx index 1460880bd9e..3a55142b090 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx @@ -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> )} diff --git a/server/sonar-web/src/main/js/apps/overview/components/IssueRating.tsx b/server/sonar-web/src/main/js/apps/overview/components/IssueRating.tsx index 9379a4078e3..05279460952 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/IssueRating.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/IssueRating.tsx @@ -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)} </> ); } diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueRating-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueRating-test.tsx index a4647657af6..1b660f3c124 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueRating-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueRating-test.tsx @@ -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> = {}) { diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueLabel-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueLabel-test.tsx.snap index 04a4cf74349..4759ea75392 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueLabel-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueLabel-test.tsx.snap @@ -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", }, } } diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueRating-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueRating-test.tsx.snap index 45be5c5c55a..f8f24334651 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueRating-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueRating-test.tsx.snap @@ -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> +`; diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx index 38c6b0b3fee..eeec9f5ea7d 100644 --- a/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx +++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx @@ -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) => ( diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/PullRequestOverview-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/PullRequestOverview-test.tsx.snap index 3d778f26731..267d6b7bd55 100644 --- a/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/PullRequestOverview-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/PullRequestOverview-test.tsx.snap @@ -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> diff --git a/server/sonar-web/src/main/js/apps/overview/utils.ts b/server/sonar-web/src/main/js/apps/overview/utils.ts index fa7c0341f9c..85028d81263 100644 --- a/server/sonar-web/src/main/js/apps/overview/utils.ts +++ b/server/sonar-web/src/main/js/apps/overview/utils.ts @@ -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 } }; diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx index f7b873d0808..8357df3a7c2 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx @@ -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"> diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx index 6fd4a355c78..9198fd8f42c 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx @@ -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"> diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardRatingMeasure.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardRatingMeasure.tsx new file mode 100644 index 00000000000..6ad1796094b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardRatingMeasure.tsx @@ -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> + ); +} diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardRatingMeasure-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardRatingMeasure-test.tsx new file mode 100644 index 00000000000..58f4dd65714 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardRatingMeasure-test.tsx @@ -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} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap index 4b2ff55782e..cf796735609 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap @@ -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" diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap index c8d30038bea..23088af1a46 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap @@ -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" diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardRatingMeasure-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardRatingMeasure-test.tsx.snap new file mode 100644 index 00000000000..730efde8faa --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardRatingMeasure-test.tsx.snap @@ -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> +`; diff --git a/server/sonar-web/src/main/js/helpers/urls.ts b/server/sonar-web/src/main/js/helpers/urls.ts index 21cac8461c2..ad1e3e5c04e 100644 --- a/server/sonar-web/src/main/js/helpers/urls.ts +++ b/server/sonar-web/src/main/js/helpers/urls.ts @@ -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 } }; } /** diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index f49347aa30c..45e6844cc53 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -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 |