Browse Source

SONAR-12961 review in project cards

tags/8.2.0.32929
Mathieu Suen 4 years ago
parent
commit
ec64094599
41 changed files with 414 additions and 136 deletions
  1. 15
    0
      server/sonar-web/src/main/js/apps/projects/components/PageSidebar.tsx
  2. 19
    0
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx
  3. 19
    0
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx
  4. 8
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap
  5. 18
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.tsx.snap
  6. 54
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap
  7. 27
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap
  8. 16
    20
      server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.tsx
  9. 3
    1
      server/sonar-web/src/main/js/apps/projects/filters/Filter.tsx
  10. 0
    5
      server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.tsx
  11. 0
    3
      server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilter.tsx
  12. 1
    1
      server/sonar-web/src/main/js/apps/projects/filters/MaintainabilityFilter.tsx
  13. 1
    1
      server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.tsx
  14. 1
    1
      server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.tsx
  15. 1
    1
      server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.tsx
  16. 0
    5
      server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilter.tsx
  17. 1
    1
      server/sonar-web/src/main/js/apps/projects/filters/ReliabilityFilter.tsx
  18. 1
    1
      server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.tsx
  19. 86
    0
      server/sonar-web/src/main/js/apps/projects/filters/SecurityReviewFilter.tsx
  20. 0
    3
      server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.tsx
  21. 1
    9
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/CoverageFilter-test.tsx
  22. 0
    3
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/IssuesFilter-test.tsx
  23. 0
    3
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/QualityGateFilter-test.tsx
  24. 30
    0
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/SecurityReviewFilter-test.tsx
  25. 6
    7
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/CoverageFilter-test.tsx.snap
  26. 0
    1
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/IssuesFilter-test.tsx.snap
  27. 0
    2
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/LanguagesFilter-test.tsx.snap
  28. 1
    1
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/MaintainabilityFilter-test.tsx.snap
  29. 1
    1
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewMaintainabilityFilter-test.tsx.snap
  30. 1
    1
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewReliabilityFilter-test.tsx.snap
  31. 1
    1
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewSecurityFilter-test.tsx.snap
  32. 0
    1
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/QualityGateFilter-test.tsx.snap
  33. 1
    1
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/ReliabilityFilter-test.tsx.snap
  34. 1
    1
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SecurityFilter-test.tsx.snap
  35. 51
    0
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SecurityReviewFilter-test.tsx.snap
  36. 0
    4
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/TagsFilter-test.tsx.snap
  37. 8
    0
      server/sonar-web/src/main/js/apps/projects/query.ts
  38. 3
    3
      server/sonar-web/src/main/js/apps/projects/styles.css
  39. 34
    50
      server/sonar-web/src/main/js/apps/projects/utils.ts
  40. 2
    2
      sonar-core/src/main/resources/org/sonar/l10n/core.properties
  41. 2
    2
      sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java

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

@@ -33,6 +33,7 @@ import NewSecurityFilter from '../filters/NewSecurityFilter';
import QualityGateFilter from '../filters/QualityGateFilter';
import ReliabilityFilter from '../filters/ReliabilityFilter';
import SecurityFilter from '../filters/SecurityFilter';
import SecurityReviewFilter from '../filters/SecurityReviewFilter';
import SizeFilter from '../filters/SizeFilter';
import TagsFilter from '../filters/TagsFilter';
import { hasFilterParams } from '../query';
@@ -91,6 +92,13 @@ export default function PageSidebar(props: Props) {
facet={getFacet(facets, 'security')}
value={query.security}
/>

<SecurityReviewFilter
{...facetProps}
facet={getFacet(facets, 'security_review_rating')}
value={query.security_review_rating}
/>

<MaintainabilityFilter
{...facetProps}
facet={getFacet(facets, 'maintainability')}
@@ -121,6 +129,13 @@ export default function PageSidebar(props: Props) {
facet={getFacet(facets, 'new_security')}
value={query.new_security}
/>
<SecurityReviewFilter
{...facetProps}
className="leak-facet-box"
facet={getFacet(facets, 'new_security_review_rating')}
property="new_security_review_rating"
value={query.new_security_review_rating}
/>
<NewMaintainabilityFilter
{...facetProps}
facet={getFacet(facets, 'new_maintainability')}

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

@@ -20,6 +20,7 @@
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';
@@ -68,6 +69,24 @@ export default function ProjectCardLeakMeasures({ measures }: Props) {
</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"
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>

<div className="project-card-measure" data-key="new_maintainability_rating">
<div className="project-card-measure-inner">
<div className="project-card-measure-number">

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

@@ -20,6 +20,7 @@
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';
@@ -81,6 +82,24 @@ export default function ProjectCardOverallMeasures({ measures }: Props) {
</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"
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>

<div className="project-card-measure" data-key="sqale_rating">
<div className="project-card-measure-inner">
<div className="project-card-measure-number">

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

@@ -93,9 +93,11 @@ exports[`renders 1`] = `
"new_maintainability": undefined,
"new_reliability": undefined,
"new_security": undefined,
"new_security_review_rating": undefined,
"reliability": undefined,
"search": undefined,
"security": undefined,
"security_review_rating": undefined,
"size": undefined,
"sort": undefined,
"tags": undefined,
@@ -148,9 +150,11 @@ exports[`renders 1`] = `
"new_maintainability": undefined,
"new_reliability": undefined,
"new_security": undefined,
"new_security_review_rating": undefined,
"reliability": undefined,
"search": undefined,
"security": undefined,
"security_review_rating": undefined,
"size": undefined,
"sort": undefined,
"tags": undefined,
@@ -384,9 +388,11 @@ exports[`renders correctly empty organization 3`] = `
"new_maintainability": undefined,
"new_reliability": undefined,
"new_security": undefined,
"new_security_review_rating": undefined,
"reliability": undefined,
"search": undefined,
"security": undefined,
"security_review_rating": undefined,
"size": undefined,
"sort": undefined,
"tags": undefined,
@@ -443,9 +449,11 @@ exports[`renders correctly empty organization 3`] = `
"new_maintainability": undefined,
"new_reliability": undefined,
"new_security": undefined,
"new_security_review_rating": undefined,
"reliability": undefined,
"search": undefined,
"security": undefined,
"security_review_rating": undefined,
"size": undefined,
"sort": undefined,
"tags": undefined,

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

@@ -40,6 +40,16 @@ exports[`should render \`leak\` view correctly 1`] = `
}
}
/>
<SecurityReviewFilter
className="leak-facet-box"
onQueryChange={[MockFunction]}
property="new_security_review_rating"
query={
Object {
"view": "leak",
}
}
/>
<NewMaintainabilityFilter
onQueryChange={[MockFunction]}
query={
@@ -128,6 +138,14 @@ exports[`should render correctly 1`] = `
}
}
/>
<SecurityReviewFilter
onQueryChange={[MockFunction]}
query={
Object {
"size": "3",
}
}
/>
<MaintainabilityFilter
onQueryChange={[MockFunction]}
query={

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

@@ -64,6 +64,33 @@ exports[`should render correctly with all data 1`] = `
</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"
@@ -230,6 +257,33 @@ exports[`should render no data style new coverage, new duplications and new line
</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"

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

@@ -64,6 +64,33 @@ exports[`should render correctly with all data 1`] = `
</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"

+ 16
- 20
server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.tsx View File

@@ -19,10 +19,6 @@
*/
import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
import {
getCoverageRatingAverageValue,
getCoverageRatingLabel
} from 'sonar-ui-common/helpers/ratings';
import CoverageRating from '../../../components/ui/CoverageRating';
import { Facet } from '../types';
import Filter from './Filter';
@@ -39,6 +35,16 @@ export interface Props {
value?: any;
}

const NO_DATA = 'NO_DATA';
const slices: { [key: string]: { label: string; average: number } } = {
'80.0-*': { label: '≥ 80%', average: 90 },
'70.0-80.0': { label: '70% - 80%', average: 75 },
'50.0-70.0': { label: '50% - 70%', average: 60 },
'30.0-50.0': { label: '30% - 50%', average: 40 },
'*-30.0': { label: '< 30%', average: 15 },
[NO_DATA]: { label: '', average: NaN }
};

export default function CoverageFilter(props: Props) {
const { property = 'coverage' } = props;

@@ -46,13 +52,12 @@ export default function CoverageFilter(props: Props) {
<Filter
className={props.className}
facet={props.facet}
getFacetValueForOption={getFacetValueForOption}
header={<FilterHeader name={translate('metric_domain.Coverage')} />}
highlightUnder={1}
highlightUnderMax={5}
maxFacetValue={props.maxFacetValue}
onQueryChange={props.onQueryChange}
options={[1, 2, 3, 4, 5, 6]}
options={Object.keys(slices)}
organization={props.organization}
property={property}
query={props.query}
@@ -62,24 +67,15 @@ export default function CoverageFilter(props: Props) {
);
}

function getFacetValueForOption(facet: Facet, option: number): number {
const map = ['80.0-*', '70.0-80.0', '50.0-70.0', '30.0-50.0', '*-30.0', 'NO_DATA'];
return facet[map[option - 1]];
}

function renderOption(option: number, selected: boolean) {
function renderOption(option: string, selected: boolean) {
return (
<span>
{option < 6 && (
<CoverageRating
muted={!selected}
size="small"
value={getCoverageRatingAverageValue(option)}
/>
{option !== NO_DATA && (
<CoverageRating muted={!selected} size="small" value={slices[option].average} />
)}
<span className="spacer-left">
{option < 6 ? (
getCoverageRatingLabel(option)
{option !== NO_DATA ? (
slices[option].label
) : (
<span className="big-spacer-left">{translate('no_data')}</span>
)}

+ 3
- 1
server/sonar-web/src/main/js/apps/projects/filters/Filter.tsx View File

@@ -49,6 +49,8 @@ interface Props {
footer?: React.ReactNode;
}

const defaultGetFacetValueForOption = (facet: Facet, option: string | number) => facet[option];

export default class Filter extends React.PureComponent<Props> {
isSelected(option: Option): boolean {
const { value } = this.props;
@@ -103,7 +105,7 @@ export default class Filter extends React.PureComponent<Props> {
}

renderOption(option: Option) {
const { facet, getFacetValueForOption, value } = this.props;
const { facet, getFacetValueForOption = defaultGetFacetValueForOption, value } = this.props;
const className = classNames(
'facet',
'search-navigator-facet',

+ 0
- 5
server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.tsx View File

@@ -42,7 +42,6 @@ export default function IssuesFilter(props: Props) {
<Filter
className={props.className}
facet={props.facet}
getFacetValueForOption={getFacetValueForOption}
header={
<FilterHeader name={translate('metric_domain', props.name)}>
{props.headerDetail}
@@ -61,10 +60,6 @@ export default function IssuesFilter(props: Props) {
);
}

function getFacetValueForOption(facet: Facet, option: number) {
return facet[option];
}

function renderOption(option: number, selected: boolean) {
return (
<span>

+ 0
- 3
server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilter.tsx View File

@@ -51,8 +51,6 @@ export default class LanguagesFilter extends React.Component<Props> {
getSortedOptions = (facet: Facet = {}) =>
sortBy(Object.keys(facet), [(option: string) => -facet[option], (option: string) => option]);

getFacetValueForOption = (facet: Facet = {}, option: string) => facet[option];

renderOption = (option: string) => (
<SearchableFilterOption
option={getLanguageByKey(this.props.languages, option)}
@@ -75,7 +73,6 @@ export default class LanguagesFilter extends React.Component<Props> {
query={this.props.query}
/>
}
getFacetValueForOption={this.getFacetValueForOption}
header={<FilterHeader name={translate('projects.facets.languages')} />}
maxFacetValue={this.props.maxFacetValue}
onQueryChange={this.props.onQueryChange}

+ 1
- 1
server/sonar-web/src/main/js/apps/projects/filters/MaintainabilityFilter.tsx View File

@@ -40,7 +40,7 @@ export default function MaintainabilityFilter(props: Props) {
{...props}
headerDetail={
<span className="note little-spacer-left">
{'('}
{'( '}
<CodeSmellIcon className="little-spacer-right" />
{translate('metric.code_smells.name')}
{' )'}

+ 1
- 1
server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.tsx View File

@@ -40,7 +40,7 @@ export default function NewMaintainabilityFilter(props: Props) {
className="leak-facet-box"
headerDetail={
<span className="note little-spacer-left">
{'('}
{'( '}
<CodeSmellIcon className="little-spacer-right" />
{translate('metric.code_smells.name')}
{' )'}

+ 1
- 1
server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.tsx View File

@@ -40,7 +40,7 @@ export default function NewReliabilityFilter(props: Props) {
className="leak-facet-box"
headerDetail={
<span className="note little-spacer-left">
{'('}
{'( '}
<BugIcon className="little-spacer-right" />
{translate('metric.bugs.name')}
{' )'}

+ 1
- 1
server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.tsx View File

@@ -40,7 +40,7 @@ export default function NewSecurityFilter(props: Props) {
className="leak-facet-box"
headerDetail={
<span className="note little-spacer-left">
{'('}
{'( '}
<VulnerabilityIcon className="little-spacer-right" />
{translate('metric.vulnerabilities.name')}
{' )'}

+ 0
- 5
server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilter.tsx View File

@@ -42,7 +42,6 @@ export default function QualityGateFilter(props: Props) {
return (
<Filter
facet={props.facet}
getFacetValueForOption={getFacetValueForOption}
header={<FilterHeader name={translate('projects.facets.quality_gate')} />}
maxFacetValue={props.maxFacetValue}
onQueryChange={props.onQueryChange}
@@ -56,10 +55,6 @@ export default function QualityGateFilter(props: Props) {
);
}

function getFacetValueForOption(facet: Facet, option: string) {
return facet[option];
}

function renderOption(option: string, selected: boolean) {
return (
<>

+ 1
- 1
server/sonar-web/src/main/js/apps/projects/filters/ReliabilityFilter.tsx View File

@@ -40,7 +40,7 @@ export default function ReliabilityFilter(props: Props) {
{...props}
headerDetail={
<span className="note little-spacer-left">
{'('}
{'( '}
<BugIcon className="little-spacer-right" />
{translate('metric.bugs.name')}
{' )'}

+ 1
- 1
server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.tsx View File

@@ -40,7 +40,7 @@ export default function SecurityFilter(props: Props) {
{...props}
headerDetail={
<span className="note little-spacer-left">
{'('}
{'( '}
<VulnerabilityIcon className="little-spacer-right" />
{translate('metric.vulnerabilities.name')}
{' )'}

+ 86
- 0
server/sonar-web/src/main/js/apps/projects/filters/SecurityReviewFilter.tsx View File

@@ -0,0 +1,86 @@
/*
* 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 SecurityHotspotIcon from 'sonar-ui-common/components/icons/SecurityHotspotIcon';
import Rating from 'sonar-ui-common/components/ui/Rating';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { Facet } from '../types';
import Filter from './Filter';
import FilterHeader from './FilterHeader';

export interface Props {
className?: string;
facet?: Facet;
maxFacetValue?: number;
onQueryChange: (change: T.RawQuery) => void;
organization?: { key: string };
property?: string;
query: T.Dict<any>;
value?: any;
}

const labels: T.Dict<string> = {
1: '≥ 80%',
2: '70% - 80%',
3: '50% - 70%',
4: '30% - 50%',
5: '< 30%'
};

export default function SecurityReviewFilter(props: Props) {
const { property = 'security_review' } = props;

return (
<Filter
className={props.className}
facet={props.facet}
header={
<FilterHeader name={translate('metric_domain.SecurityReview')}>
{
<span className="note little-spacer-left">
{'( '}
<SecurityHotspotIcon className="little-spacer-right" />
{translate('metric.security_hotspots.name')}
{' )'}
</span>
}
</FilterHeader>
}
highlightUnder={1}
maxFacetValue={props.maxFacetValue}
onQueryChange={props.onQueryChange}
options={[1, 2, 3, 4, 5]}
organization={props.organization}
property={property}
query={props.query}
renderOption={renderOption}
value={props.value}
/>
);
}

function renderOption(option: number, selected: boolean) {
return (
<span>
<Rating muted={!selected} small={true} value={option} />
<span className="spacer-left">{labels[option]}</span>
</span>
);
}

+ 0
- 3
server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.tsx View File

@@ -95,8 +95,6 @@ export default class TagsFilter extends React.PureComponent<Props, State> {
getSortedOptions = (facet: Facet = {}) =>
sortBy(Object.keys(facet), [(option: string) => -facet[option], (option: string) => option]);

getFacetValueForOption = (facet: Facet = {}, option: string) => facet[option];

renderOption = (option: string) => <SearchableFilterOption optionKey={option} />;

render() {
@@ -117,7 +115,6 @@ export default class TagsFilter extends React.PureComponent<Props, State> {
query={this.props.query}
/>
}
getFacetValueForOption={this.getFacetValueForOption}
header={<FilterHeader name={translate('projects.facets.tags')} />}
maxFacetValue={this.props.maxFacetValue}
onQueryChange={this.props.onQueryChange}

+ 1
- 9
server/sonar-web/src/main/js/apps/projects/filters/__tests__/CoverageFilter-test.tsx View File

@@ -26,13 +26,5 @@ it('renders', () => {
expect(wrapper).toMatchSnapshot();

const renderOption = wrapper.prop('renderOption');
expect(renderOption(2, false)).toMatchSnapshot();

const getFacetValueForOption = wrapper.prop('getFacetValueForOption');
expect(
getFacetValueForOption(
{ '80.0-*': 1, '70.0-80.0': 42, '50.0-70.0': 14, '30.0-50.0': 13, '*-30.0': 8, NO_DATA: 3 },
2
)
).toBe(42);
expect(renderOption('70.0-80.0', false)).toMatchSnapshot();
});

+ 0
- 3
server/sonar-web/src/main/js/apps/projects/filters/__tests__/IssuesFilter-test.tsx View File

@@ -29,7 +29,4 @@ it('renders', () => {

const renderOption = wrapper.prop('renderOption');
expect(renderOption(2, false)).toMatchSnapshot();

const getFacetValueForOption = wrapper.prop('getFacetValueForOption');
expect(getFacetValueForOption({ 1: 1, 2: 2, 3: 3, 4: 4, 5: 5 }, 2)).toBe(2);
});

+ 0
- 3
server/sonar-web/src/main/js/apps/projects/filters/__tests__/QualityGateFilter-test.tsx View File

@@ -27,9 +27,6 @@ it('renders', () => {

const renderOption = wrapper.prop('renderOption');
expect(renderOption(2, false)).toMatchSnapshot();

const getFacetValueForOption = wrapper.prop('getFacetValueForOption');
expect(getFacetValueForOption({ ERROR: 1, OK: 3 }, 'OK')).toBe(3);
});

it('should render with warning facet', () => {

+ 30
- 0
server/sonar-web/src/main/js/apps/projects/filters/__tests__/SecurityReviewFilter-test.tsx View File

@@ -0,0 +1,30 @@
/*
* 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 SecurityReviewFilter from '../SecurityReviewFilter';

it('renders', () => {
const wrapper = shallow(<SecurityReviewFilter onQueryChange={jest.fn()} query={{}} />);
expect(wrapper).toMatchSnapshot();

const renderOption = wrapper.prop('renderOption');
expect(renderOption(2, false)).toMatchSnapshot('option');
});

+ 6
- 7
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/CoverageFilter-test.tsx.snap View File

@@ -2,7 +2,6 @@

exports[`renders 1`] = `
<Filter
getFacetValueForOption={[Function]}
header={
<FilterHeader
name="metric_domain.Coverage"
@@ -13,12 +12,12 @@ exports[`renders 1`] = `
onQueryChange={[MockFunction]}
options={
Array [
1,
2,
3,
4,
5,
6,
"80.0-*",
"70.0-80.0",
"50.0-70.0",
"30.0-50.0",
"*-30.0",
"NO_DATA",
]
}
property="coverage"

+ 0
- 1
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/IssuesFilter-test.tsx.snap View File

@@ -2,7 +2,6 @@

exports[`renders 1`] = `
<Filter
getFacetValueForOption={[Function]}
header={
<FilterHeader
name="metric_domain.bugs"

+ 0
- 2
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/LanguagesFilter-test.tsx.snap View File

@@ -39,7 +39,6 @@ exports[`should render the languages facet with the selected languages 1`] = `
}
/>
}
getFacetValueForOption={[Function]}
header={
<FilterHeader
name="projects.facets.languages"
@@ -229,7 +228,6 @@ exports[`should render the languages without the ones in the facet 1`] = `
}
/>
}
getFacetValueForOption={[Function]}
header={
<FilterHeader
name="projects.facets.languages"

+ 1
- 1
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/MaintainabilityFilter-test.tsx.snap View File

@@ -6,7 +6,7 @@ exports[`renders 1`] = `
<span
className="note little-spacer-left"
>
(
(
<CodeSmellIcon
className="little-spacer-right"
/>

+ 1
- 1
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewMaintainabilityFilter-test.tsx.snap View File

@@ -7,7 +7,7 @@ exports[`renders 1`] = `
<span
className="note little-spacer-left"
>
(
(
<CodeSmellIcon
className="little-spacer-right"
/>

+ 1
- 1
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewReliabilityFilter-test.tsx.snap View File

@@ -7,7 +7,7 @@ exports[`renders 1`] = `
<span
className="note little-spacer-left"
>
(
(
<BugIcon
className="little-spacer-right"
/>

+ 1
- 1
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewSecurityFilter-test.tsx.snap View File

@@ -7,7 +7,7 @@ exports[`renders 1`] = `
<span
className="note little-spacer-left"
>
(
(
<VulnerabilityIcon
className="little-spacer-right"
/>

+ 0
- 1
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/QualityGateFilter-test.tsx.snap View File

@@ -2,7 +2,6 @@

exports[`renders 1`] = `
<Filter
getFacetValueForOption={[Function]}
header={
<FilterHeader
name="projects.facets.quality_gate"

+ 1
- 1
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/ReliabilityFilter-test.tsx.snap View File

@@ -6,7 +6,7 @@ exports[`renders 1`] = `
<span
className="note little-spacer-left"
>
(
(
<BugIcon
className="little-spacer-right"
/>

+ 1
- 1
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SecurityFilter-test.tsx.snap View File

@@ -6,7 +6,7 @@ exports[`renders 1`] = `
<span
className="note little-spacer-left"
>
(
(
<VulnerabilityIcon
className="little-spacer-right"
/>

+ 51
- 0
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SecurityReviewFilter-test.tsx.snap View File

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

exports[`renders 1`] = `
<Filter
header={
<FilterHeader
name="metric_domain.SecurityReview"
>
<span
className="note little-spacer-left"
>
(
<SecurityHotspotIcon
className="little-spacer-right"
/>
metric.security_hotspots.name
)
</span>
</FilterHeader>
}
highlightUnder={1}
onQueryChange={[MockFunction]}
options={
Array [
1,
2,
3,
4,
5,
]
}
property="security_review"
query={Object {}}
renderOption={[Function]}
/>
`;

exports[`renders: option 1`] = `
<span>
<Rating
muted={true}
small={true}
value={2}
/>
<span
className="spacer-left"
>
70% - 80%
</span>
</span>
`;

+ 0
- 4
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/TagsFilter-test.tsx.snap View File

@@ -71,7 +71,6 @@ exports[`should render maximum 10 tags in the searchbox results 1`] = `
}
/>
}
getFacetValueForOption={[Function]}
header={
<FilterHeader
name="projects.facets.tags"
@@ -132,7 +131,6 @@ exports[`should render the tags facet with the selected tags 1`] = `
}
/>
}
getFacetValueForOption={[Function]}
header={
<FilterHeader
name="projects.facets.tags"
@@ -280,7 +278,6 @@ exports[`should render the tags without the ones in the facet 1`] = `
}
/>
}
getFacetValueForOption={[Function]}
header={
<FilterHeader
name="projects.facets.tags"
@@ -343,7 +340,6 @@ exports[`should render the tags without the ones in the facet 2`] = `
}
/>
}
getFacetValueForOption={[Function]}
header={
<FilterHeader
name="projects.facets.tags"

+ 8
- 0
server/sonar-web/src/main/js/apps/projects/query.ts View File

@@ -27,6 +27,8 @@ export interface Query {
new_reliability?: number;
security?: number;
new_security?: number;
security_review_rating?: number;
new_security_review_rating?: number;
maintainability?: number;
new_maintainability?: number;
coverage?: number;
@@ -51,6 +53,8 @@ export function parseUrlQuery(urlQuery: T.RawQuery): Query {
new_reliability: getAsNumericRating(urlQuery['new_reliability']),
security: getAsNumericRating(urlQuery['security']),
new_security: getAsNumericRating(urlQuery['new_security']),
security_review_rating: getAsNumericRating(urlQuery['security_review']),
new_security_review_rating: getAsNumericRating(urlQuery['new_security_review']),
maintainability: getAsNumericRating(urlQuery['maintainability']),
new_maintainability: getAsNumericRating(urlQuery['new_maintainability']),
coverage: getAsNumericRating(urlQuery['coverage']),
@@ -94,9 +98,11 @@ export function convertToFilter(query: Query, isFavorite: boolean): string {
[
'reliability',
'security',
'security_review_rating',
'maintainability',
'new_reliability',
'new_security',
'new_security_review_rating',
'new_maintainability'
].forEach(property => pushMetricToArray(query, property, conditions, convertIssuesRating));

@@ -232,6 +238,8 @@ function mapPropertyToMetric(property?: string): string | undefined {
new_reliability: 'new_reliability_rating',
security: 'security_rating',
new_security: 'new_security_rating',
security_review_rating: 'security_review_rating',
new_security_review_rating: 'new_security_review_rating',
maintainability: 'sqale_rating',
new_maintainability: 'new_maintainability_rating',
coverage: 'coverage',

+ 3
- 3
server/sonar-web/src/main/js/apps/projects/styles.css View File

@@ -93,7 +93,7 @@
.project-card-measures {
display: flex;
padding-top: 8px;
margin: 0 -15px;
margin: 0 -15px 0 -30px;
}

.project-card-leak-measures {
@@ -114,7 +114,7 @@
}

.project-card-measure {
width: 130px;
flex: 0 1 15%;
}

.project-card-measure {
@@ -123,7 +123,7 @@
vertical-align: top;
text-align: center;
box-sizing: border-box;
padding: 0 15px;
padding: 0 var(--gridSize);
}

.project-card-measure + .project-card-measure:before {

+ 34
- 50
server/sonar-web/src/main/js/apps/projects/utils.ts View File

@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { uniq } from 'lodash';
import { invert, uniq } from 'lodash';
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
import { RequestData } from 'sonar-ui-common/helpers/request';
import { Facet, searchProjects } from '../../api/components';
@@ -97,6 +97,8 @@ const METRICS = [
'reliability_rating',
'vulnerabilities',
'security_rating',
'security_hotspots_reviewed',
'security_review_rating',
'code_smells',
'sqale_rating',
'duplicated_lines_density',
@@ -111,6 +113,8 @@ const LEAK_METRICS = [
'new_reliability_rating',
'new_vulnerabilities',
'new_security_rating',
'new_security_hotspots_reviewed',
'new_security_review_rating',
'new_code_smells',
'new_maintainability_rating',
'new_coverage',
@@ -131,6 +135,7 @@ const METRICS_BY_VISUALIZATION: T.Dict<string[]> = {
export const FACETS = [
'reliability_rating',
'security_rating',
'security_review_rating',
'sqale_rating',
'coverage',
'duplicated_lines_density',
@@ -143,6 +148,7 @@ export const FACETS = [
export const LEAK_FACETS = [
'new_reliability_rating',
'new_security_rating',
'new_security_review_rating',
'new_maintainability_rating',
'new_coverage',
'new_duplicated_lines_density',
@@ -284,10 +290,34 @@ function mapFacetValues(values: Array<{ val: string; count: number }>) {
return map;
}

const propertyToMetricMap: T.Dict<string | undefined> = {
analysis_date: 'analysisDate',
reliability: 'reliability_rating',
new_reliability: 'new_reliability_rating',
security: 'security_rating',
new_security: 'new_security_rating',
security_review_rating: 'security_review_rating',
new_security_review_rating: 'new_security_review_rating',
maintainability: 'sqale_rating',
new_maintainability: 'new_maintainability_rating',
coverage: 'coverage',
new_coverage: 'new_coverage',
duplications: 'duplicated_lines_density',
new_duplications: 'new_duplicated_lines_density',
size: 'ncloc',
new_lines: 'new_lines',
gate: 'alert_status',
languages: 'languages',
tags: 'tags',
search: 'query'
};

const metricToPropertyMap = invert(propertyToMetricMap);

function getFacetsMap(facets: Facet[]) {
const map: T.Dict<T.Dict<number>> = {};
facets.forEach(facet => {
const property = mapMetricToProperty(facet.property);
const property = metricToPropertyMap[facet.property];
const { values } = facet;
if (REVERSED_FACETS.includes(property)) {
values.reverse();
@@ -297,57 +327,11 @@ function getFacetsMap(facets: Facet[]) {
return map;
}

function mapPropertyToMetric(property?: string) {
const map: T.Dict<string> = {
analysis_date: 'analysisDate',
reliability: 'reliability_rating',
new_reliability: 'new_reliability_rating',
security: 'security_rating',
new_security: 'new_security_rating',
maintainability: 'sqale_rating',
new_maintainability: 'new_maintainability_rating',
coverage: 'coverage',
new_coverage: 'new_coverage',
duplications: 'duplicated_lines_density',
new_duplications: 'new_duplicated_lines_density',
size: 'ncloc',
new_lines: 'new_lines',
gate: 'alert_status',
languages: 'languages',
tags: 'tags',
search: 'query'
};
return property && map[property];
}

function convertToSorting({ sort }: Query): { s?: string; asc?: boolean } {
if (sort && sort[0] === '-') {
return { s: mapPropertyToMetric(sort.substr(1)), asc: false };
return { s: propertyToMetricMap[sort.substr(1)], asc: false };
}
return { s: mapPropertyToMetric(sort) };
}

function mapMetricToProperty(metricKey: string) {
const map: T.Dict<string> = {
analysisDate: 'analysis_date',
reliability_rating: 'reliability',
new_reliability_rating: 'new_reliability',
security_rating: 'security',
new_security_rating: 'new_security',
sqale_rating: 'maintainability',
new_maintainability_rating: 'new_maintainability',
coverage: 'coverage',
new_coverage: 'new_coverage',
duplicated_lines_density: 'duplications',
new_duplicated_lines_density: 'new_duplications',
ncloc: 'size',
new_lines: 'new_lines',
alert_status: 'gate',
languages: 'languages',
tags: 'tags',
query: 'search'
};
return map[metricKey];
return { s: propertyToMetricMap[sort || ''] };
}

const ONE_MINUTE = 60000;

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

@@ -2111,7 +2111,7 @@ metric.new_reliability_remediation_effort.extra_short_name=Remediation Effort
metric.new_security_hotspots.description=New Security Hotspots
metric.new_security_hotspots.name=New Security Hotspots
metric.new_security_hotspots.short_name=Security Hotspots
metric.new_security_hotspots_reviewed.description=Security Hotspots Reviewed on New Code
metric.new_security_hotspots_reviewed.description=Percentage of Security Hotspots Reviewed on New Code
metric.new_security_hotspots_reviewed.name=Security Hotspots Reviewed on New Code
metric.new_security_hotspots_reviewed.short_name=Security Hotspots Reviewed
metric.new_security_rating.description=Security rating on new code
@@ -2221,7 +2221,7 @@ metric.rfc_distribution.description=Class distribution /RFC
metric.rfc_distribution.name=Class Distribution / RFC
metric.security_hotspots.description=Security Hotspots
metric.security_hotspots.name=Security Hotspots
metric.security_hotspots_reviewed.description=Security Hotspots Reviewed
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_rating.description=Security rating

+ 2
- 2
sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java View File

@@ -1563,7 +1563,7 @@ public final class CoreMetrics {
* @since 8.2
*/
public static final Metric<Integer> SECURITY_HOTSPOTS_REVIEWED = new Metric.Builder(SECURITY_HOTSPOTS_REVIEWED_KEY, "Security Hotspots Reviewed", Metric.ValueType.PERCENT)
.setDescription("Security Hotspots Reviewed")
.setDescription("Percentage of Security Hotspots Reviewed")
.setDomain(DOMAIN_SECURITY_REVIEW)
.setDirection(Metric.DIRECTION_BETTER)
.setQualitative(true)
@@ -1581,7 +1581,7 @@ public final class CoreMetrics {
*/
public static final Metric<Integer> NEW_SECURITY_HOTSPOTS_REVIEWED = new Metric.Builder(NEW_SECURITY_HOTSPOTS_REVIEWED_KEY, "Security Hotspots Reviewed on New Code",
Metric.ValueType.PERCENT)
.setDescription("Security Hotspots Reviewed on New Code")
.setDescription("Percentage of Security Hotspots Reviewed on New Code")
.setDomain(DOMAIN_SECURITY_REVIEW)
.setDirection(Metric.DIRECTION_BETTER)
.setDeleteHistoricalData(true)

Loading…
Cancel
Save