Sfoglia il codice sorgente

SONAR-13421 Improve project cards

tags/8.5.0.37579
Mathieu Suen 4 anni fa
parent
commit
ad4424906d
36 ha cambiato i file con 1673 aggiunte e 2022 eliminazioni
  1. 12
    0
      server/sonar-web/src/main/js/app/styles/init/misc.css
  2. 0
    200
      server/sonar-web/src/main/js/apps/projects/components/ProjectCard.tsx
  3. 0
    105
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx
  4. 0
    39
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganization.tsx
  5. 0
    140
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx
  6. 0
    59
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardRatingMeasure.tsx
  7. 8
    10
      server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx
  8. 0
    53
      server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeakMeasures-test.tsx
  9. 0
    71
      server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverallMeasures-test.tsx
  10. 0
    47
      server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardRatingMeasure-test.tsx
  11. 0
    416
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCard-test.tsx.snap
  12. 0
    41
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap
  13. 0
    278
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap
  14. 0
    232
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap
  15. 0
    21
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardQualityGate-test.tsx.snap
  16. 0
    69
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardRatingMeasure-test.tsx.snap
  17. 3
    3
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap
  18. 70
    0
      server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.css
  19. 242
    0
      server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx
  20. 4
    22
      server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardLanguages.tsx
  21. 1
    1
      server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardLanguagesContainer.tsx
  22. 55
    0
      server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasure.tsx
  23. 201
    0
      server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasures.tsx
  24. 10
    18
      server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardQualityGate.tsx
  25. 16
    26
      server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCard-test.tsx
  26. 0
    0
      server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardLanguages-test.tsx
  27. 12
    6
      server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardMeasure-test.tsx
  28. 99
    0
      server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardMeasures-test.tsx
  29. 0
    0
      server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardQualityGate-test.tsx
  30. 540
    0
      server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCard-test.tsx.snap
  31. 33
    0
      server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap
  32. 38
    0
      server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardMeasure-test.tsx.snap
  33. 314
    0
      server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardMeasures-test.tsx.snap
  34. 14
    0
      server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardQualityGate-test.tsx.snap
  35. 0
    163
      server/sonar-web/src/main/js/apps/projects/styles.css
  36. 1
    2
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 12
- 0
server/sonar-web/src/main/js/app/styles/init/misc.css Vedi File

@@ -179,11 +179,18 @@ th.hide-overflow {
.big-padded-top {
padding-top: calc(2 * var(--gridSize));
}
.big-padded-bottom {
padding-bottom: calc(2 * var(--gridSize));
}

.big-padded-right {
padding-right: calc(2 * var(--gridSize));
}

.big-padded-left {
padding-left: calc(2 * var(--gridSize));
}

.huge-padded-top {
padding-top: 40px;
}
@@ -430,6 +437,11 @@ th.huge-spacer-right {
align-items: flex-start;
}

.display-flex-end {
display: flex !important;
align-items: flex-end;
}

.display-flex-wrap {
display: flex !important;
flex-wrap: wrap;

+ 0
- 200
server/sonar-web/src/main/js/apps/projects/components/ProjectCard.tsx Vedi File

@@ -1,200 +0,0 @@
/*
* 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 classNames from 'classnames';
import * as difference from 'date-fns/difference_in_milliseconds';
import * as React from 'react';
import { Link } from 'react-router';
import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon';
import DateTimeFormatter from 'sonar-ui-common/components/intl/DateTimeFormatter';
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
import PrivacyBadgeContainer from '../../../components/common/PrivacyBadgeContainer';
import Favorite from '../../../components/controls/Favorite';
import TagsList from '../../../components/tags/TagsList';
import { getProjectUrl } from '../../../helpers/urls';
import { isLoggedIn } from '../../../helpers/users';
import { ComponentQualifier } from '../../../types/component';
import { Project } from '../types';
import { formatDuration } from '../utils';
import ProjectCardLeakMeasures from './ProjectCardLeakMeasures';
import ProjectCardOverallMeasures from './ProjectCardOverallMeasures';
import ProjectCardQualityGate from './ProjectCardQualityGate';

interface Props {
currentUser: T.CurrentUser;
handleFavorite: (component: string, isFavorite: boolean) => void;
height: number;
project: Project;
type?: string;
}

interface Dates {
analysisDate: string;
leakPeriodDate?: string;
}

function getDates(project: Project, type: string | undefined) {
const { analysisDate, leakPeriodDate } = project;
if (!analysisDate || (type === 'leak' && !leakPeriodDate)) {
return undefined;
} else {
return { analysisDate, leakPeriodDate };
}
}

function renderHeader(props: Props) {
const { project } = props;
const hasTags = project.tags.length > 0;
return (
<div className="project-card-header">
{project.isFavorite !== undefined && (
<Favorite
className="spacer-right"
component={project.key}
favorite={project.isFavorite}
handleFavorite={props.handleFavorite}
qualifier={project.qualifier}
/>
)}
<h2 className="project-card-name">
{props.project.needIssueSync ? (
props.project.name
) : (
<Link to={getProjectUrl(project.key)}>{props.project.name}</Link>
)}
</h2>
{project.analysisDate && <ProjectCardQualityGate status={project.measures['alert_status']} />}
<div className="project-card-header-right">
<PrivacyBadgeContainer
className="spacer-left"
qualifier={project.qualifier}
visibility={project.visibility}
/>

{hasTags && <TagsList className="spacer-left note" tags={project.tags} />}
</div>
</div>
);
}

function renderDates(dates: Dates, type: string | undefined) {
const { analysisDate, leakPeriodDate } = dates;
const periodMs = leakPeriodDate ? difference(Date.now(), leakPeriodDate) : 0;

return (
<>
<DateTimeFormatter date={analysisDate}>
{formattedDate => (
<span className="note">
{translateWithParameters('projects.last_analysis_on_x', formattedDate)}
</span>
)}
</DateTimeFormatter>
{type === 'leak' && periodMs !== undefined && (
<span className="project-card-leak-date big-spacer-left big-spacer-right">
{translateWithParameters('projects.new_code_period_x', formatDuration(periodMs))}
</span>
)}
</>
);
}

function renderDateRow(project: Project, dates: Dates | undefined, type: string | undefined) {
if (project.qualifier === ComponentQualifier.Application || dates) {
return (
<div
className={classNames('display-flex-center project-card-dates spacer-top', {
'big-spacer-left padded-left': project.isFavorite !== undefined
})}>
{dates && renderDates(dates, type)}

{project.qualifier === ComponentQualifier.Application && (
<div className="text-right flex-1-0-auto">
<QualifierIcon className="spacer-right" qualifier={project.qualifier} />
{translate('qualifier.APP')}
{project.measures.projects && (
<>
{' ‒ '}
{translateWithParameters('x_projects_', project.measures.projects)}
</>
)}
</div>
)}
</div>
);
} else {
return null;
}
}

function renderMeasures(props: Props, dates: Dates | undefined) {
const { currentUser, project, type } = props;

const { measures } = project;

if (dates) {
return type === 'leak' ? (
<ProjectCardLeakMeasures measures={measures} />
) : (
<ProjectCardOverallMeasures componentQualifier={project.qualifier} measures={measures} />
);
} else {
return (
<div className="project-card-not-analyzed">
<span className="note">
{type === 'leak' && project.analysisDate
? translate('projects.no_new_code_period', project.qualifier)
: translate('projects.not_analyzed', project.qualifier)}
</span>
{project.qualifier !== ComponentQualifier.Application &&
!project.analysisDate &&
isLoggedIn(currentUser) && (
<Link className="button spacer-left" to={getProjectUrl(project.key)}>
{translate('projects.configure_analysis')}
</Link>
)}
</div>
);
}
}

export default function ProjectCard(props: Props) {
const { height, project, type } = props;
const { needIssueSync, key } = project;

const dates = getDates(project, type);

return (
<div
className={classNames(
'boxed-group project-card big-padded display-flex-column display-flex-space-between',
{
'need-issue-sync': needIssueSync
}
)}
data-key={key}
style={{ height }}>
<div>
{renderHeader(props)}
{renderDateRow(project, dates, type)}
</div>
{renderMeasures(props, dates)}
</div>
);
}

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

@@ -1,105 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
import Measure from '../../../components/measure/Measure';
import ProjectCardRatingMeasure from './ProjectCardRatingMeasure';

interface Props {
measures: T.Dict<string>;
}

export default function ProjectCardLeakMeasures({ measures }: Props) {
return (
<div className="project-card-leak-measures">
<ProjectCardRatingMeasure
iconLabel={translate('metric.bugs.name')}
measures={measures}
metricKey="new_bugs"
metricRatingKey="new_reliability_rating"
metricType="SHORT_INT"
/>

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

<ProjectCardRatingMeasure
iconKey="security_hotspots"
iconLabel={translate('projects.security_hotspots_reviewed')}
measures={measures}
metricKey="new_security_hotspots_reviewed"
metricRatingKey="new_security_review_rating"
metricType="PERCENT"
/>

<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">
<div className="project-card-measure-inner">
<div className="project-card-measure-number">
<Measure
metricKey="new_coverage"
metricType="PERCENT"
value={measures['new_coverage']}
/>
</div>
<div className="project-card-measure-label">{translate('metric.coverage.name')}</div>
</div>
</div>
)}

<div className="project-card-measure" data-key="new_duplicated_lines_density">
<div className="project-card-measure-inner">
<div className="project-card-measure-number">
<Measure
metricKey="new_duplicated_lines_density"
metricType="PERCENT"
value={measures['new_duplicated_lines_density']}
/>
</div>
<div className="project-card-measure-label">
{translate('metric.duplicated_lines_density.short_name')}
</div>
</div>
</div>

<div className="project-card-measure project-card-ncloc" data-key="new_lines">
<div className="project-card-measure-inner">
<div className="project-card-measure-number">
<Measure metricKey="new_lines" metricType="SHORT_INT" value={measures['new_lines']} />
</div>
<div className="project-card-measure-label">{translate('metric.lines.name')}</div>
</div>
</div>
</div>
);
}

+ 0
- 39
server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganization.tsx Vedi File

@@ -1,39 +0,0 @@
/*
* 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 OrganizationLink from '../../../components/ui/OrganizationLink';

interface Props {
organization?: { key: string; name: string };
organizationsEnabled?: boolean;
}

export default function ProjectCardOrganization({ organization, organizationsEnabled }: Props) {
if (!organization || !organizationsEnabled) {
return null;
}

return (
<span className="text-normal">
<OrganizationLink organization={organization}>{organization.name}</OrganizationLink>
<span className="slash-separator" />
</span>
);
}

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

@@ -1,140 +0,0 @@
/*
* 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 DuplicationsRating from 'sonar-ui-common/components/ui/DuplicationsRating';
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 { ComponentQualifier } from '../../../types/component';
import ProjectCardLanguagesContainer from './ProjectCardLanguagesContainer';
import ProjectCardRatingMeasure from './ProjectCardRatingMeasure';

interface Props {
componentQualifier: ComponentQualifier;
measures: T.Dict<string | undefined>;
}

export default function ProjectCardOverallMeasures({ componentQualifier, measures }: Props) {
if (measures === undefined) {
return null;
}

const { ncloc } = measures;
if (!ncloc) {
return (
<div className="note big-spacer-top">
{componentQualifier === ComponentQualifier.Application
? translate('portfolio.app.empty')
: translate('overview.project.main_branch_empty')}
</div>
);
}

return (
<div className="project-card-measures">
<ProjectCardRatingMeasure
iconLabel={translate('metric.bugs.name')}
measures={measures}
metricKey="bugs"
metricRatingKey="reliability_rating"
metricType="SHORT_INT"
/>

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

<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"
/>

{measures['coverage'] != null && (
<div className="project-card-measure" data-key="coverage">
<div className="project-card-measure-inner">
<div className="project-card-measure-number">
<span className="spacer-right">
<CoverageRating value={measures['coverage']} />
</span>
<Measure metricKey="coverage" metricType="PERCENT" value={measures['coverage']} />
</div>
<div className="project-card-measure-label">{translate('metric.coverage.name')}</div>
</div>
</div>
)}

<div className="project-card-measure" data-key="duplicated_lines_density">
<div className="project-card-measure-inner">
<div className="project-card-measure-number">
{measures['duplicated_lines_density'] != null && (
<span className="spacer-right">
<DuplicationsRating value={Number(measures['duplicated_lines_density'])} />
</span>
)}
<Measure
metricKey="duplicated_lines_density"
metricType="PERCENT"
value={measures['duplicated_lines_density']}
/>
</div>
<div className="project-card-measure-label">
{translate('metric.duplicated_lines_density.short_name')}
</div>
</div>
</div>

{measures['ncloc'] != null && (
<div className="project-card-measure project-card-ncloc" data-key="ncloc">
<div className="project-card-measure-inner pull-right">
<div className="project-card-measure-number">
<Measure metricKey="ncloc" metricType="SHORT_INT" value={measures['ncloc']} />
<span className="spacer-left">
<SizeRating value={Number(measures['ncloc'])} />
</span>
</div>
<div className="project-card-measure-label">
<ProjectCardLanguagesContainer
className="project-card-languages"
distribution={measures['ncloc_language_distribution']}
/>
</div>
</div>
</div>
)}
</div>
);
}

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

@@ -1,59 +0,0 @@
/*
* 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>
);
}

+ 8
- 10
server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx Vedi File

@@ -28,7 +28,10 @@ import { Project } from '../types';
import EmptyFavoriteSearch from './EmptyFavoriteSearch';
import EmptyInstance from './EmptyInstance';
import NoFavoriteProjects from './NoFavoriteProjects';
import ProjectCard from './ProjectCard';
import ProjectCard from './project-card/ProjectCard';

const PROJECT_CARD_HEIGHT = 145;
const PROJECT_CARD_MARGIN = 20;

interface Props {
cardType?: string;
@@ -41,10 +44,6 @@ interface Props {
}

export default class ProjectsList extends React.PureComponent<Props> {
getCardHeight = () => {
return this.props.cardType === 'leak' ? 159 : 143;
};

renderNoProjects() {
const { currentUser, isFavorite, isFiltered, query } = this.props;
if (isFiltered) {
@@ -55,14 +54,14 @@ export default class ProjectsList extends React.PureComponent<Props> {

renderRow = ({ index, key, style }: ListRowProps) => {
const project = this.props.projects[index];
const height = this.getCardHeight();
return (
<div key={key} role="row" style={{ ...style, height }}>
<div key={key} role="row" style={{ ...style, height: PROJECT_CARD_HEIGHT }}>
<div role="gridcell">
<ProjectCard
currentUser={this.props.currentUser}
handleFavorite={this.props.handleFavorite}
height={height}
height={PROJECT_CARD_HEIGHT}
key={project.key}
project={project}
type={this.props.cardType}
@@ -73,7 +72,6 @@ export default class ProjectsList extends React.PureComponent<Props> {
};

renderList() {
const cardHeight = this.getCardHeight();
return (
<WindowScroller>
{({ height, isScrolling, onChildScroll, scrollTop }) => (
@@ -88,7 +86,7 @@ export default class ProjectsList extends React.PureComponent<Props> {
onScroll={onChildScroll}
overscanRowCount={2}
rowCount={this.props.projects.length}
rowHeight={cardHeight + 20}
rowHeight={PROJECT_CARD_HEIGHT + PROJECT_CARD_MARGIN}
rowRenderer={this.renderRow}
scrollTop={scrollTop}
style={{ outline: 'none' }}

+ 0
- 53
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeakMeasures-test.tsx Vedi File

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

it('should render correctly with all data', () => {
const measures = {
alert_status: 'ERROR',
new_reliability_rating: '1.0',
new_bugs: '8',
new_security_rating: '2.0',
new_vulnerabilities: '2',
new_maintainability_rating: '1.0',
new_code_smells: '0',
new_coverage: '26.55',
new_duplicated_lines_density: '0.55',
new_lines: '87'
};
const wrapper = shallow(<ProjectCardLeakMeasures measures={measures} />);
expect(wrapper).toMatchSnapshot();
});

it('should render no data style new coverage, new duplications and new lines', () => {
const measures = {
alert_status: 'ERROR',
new_reliability_rating: '1.0',
new_bugs: '8',
new_security_rating: '2.0',
new_vulnerabilities: '2',
new_maintainability_rating: '1.0',
new_code_smells: '0'
};
const wrapper = shallow(<ProjectCardLeakMeasures measures={measures} />);
expect(wrapper).toMatchSnapshot();
});

+ 0
- 71
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverallMeasures-test.tsx Vedi File

@@ -1,71 +0,0 @@
/*
* 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 { ComponentQualifier } from '../../../../types/component';
import ProjectCardOverallMeasures from '../ProjectCardOverallMeasures';

it('should render correctly with all data', () => {
expect(shallowRender()).toMatchSnapshot();
});

it('should not render coverage', () => {
expect(
shallowRender({ coverage: undefined })
.find('[data-key="coverage"]')
.exists()
).toBe(false);
});

it('should render empty', () => {
expect(shallowRender({ ncloc: undefined })).toMatchSnapshot('project');
expect(shallowRender({ ncloc: undefined }, ComponentQualifier.Application)).toMatchSnapshot(
'application'
);
});

it('should render ncloc correctly', () => {
expect(shallowRender({ ncloc: '16549887' }).find('[data-key="ncloc"]')).toMatchSnapshot();
});

function shallowRender(
overriddenMeasures: T.Dict<string | undefined> = {},
componentQualifier?: ComponentQualifier
) {
const measures = {
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',
...overriddenMeasures
};
return shallow(
<ProjectCardOverallMeasures
componentQualifier={componentQualifier ?? ComponentQualifier.Project}
measures={measures}
/>
);
}

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

@@ -1,47 +0,0 @@
/*
* 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}
/>
);
}

+ 0
- 416
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCard-test.tsx.snap Vedi File

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

exports[`should display applications 1`] = `
<div
className="boxed-group project-card big-padded display-flex-column display-flex-space-between"
data-key="foo"
style={
Object {
"height": 100,
}
}
>
<div>
<div
className="project-card-header"
>
<h2
className="project-card-name"
>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "foo",
},
}
}
>
Foo
</Link>
</h2>
<ProjectCardQualityGate
status="OK"
/>
<div
className="project-card-header-right"
>
<PrivacyBadgeContainer
className="spacer-left"
qualifier="APP"
visibility="public"
/>
</div>
</div>
<div
className="display-flex-center project-card-dates spacer-top"
>
<DateTimeFormatter
date="2017-01-01"
>
<Component />
</DateTimeFormatter>
<div
className="text-right flex-1-0-auto"
>
<QualifierIcon
className="spacer-right"
qualifier="APP"
/>
qualifier.APP
</div>
</div>
</div>
<ProjectCardOverallMeasures
componentQualifier="APP"
measures={
Object {
"alert_status": "OK",
"new_bugs": "12",
"reliability_rating": "1.0",
"sqale_rating": "1.0",
}
}
/>
</div>
`;

exports[`should display applications: with project count 1`] = `
<div
className="boxed-group project-card big-padded display-flex-column display-flex-space-between"
data-key="foo"
style={
Object {
"height": 100,
}
}
>
<div>
<div
className="project-card-header"
>
<h2
className="project-card-name"
>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "foo",
},
}
}
>
Foo
</Link>
</h2>
<ProjectCardQualityGate
status="OK"
/>
<div
className="project-card-header-right"
>
<PrivacyBadgeContainer
className="spacer-left"
qualifier="APP"
visibility="public"
/>
</div>
</div>
<div
className="display-flex-center project-card-dates spacer-top"
>
<DateTimeFormatter
date="2017-01-01"
>
<Component />
</DateTimeFormatter>
<div
className="text-right flex-1-0-auto"
>
<QualifierIcon
className="spacer-right"
qualifier="APP"
/>
qualifier.APP
x_projects_.3
</div>
</div>
</div>
<ProjectCardOverallMeasures
componentQualifier="APP"
measures={
Object {
"alert_status": "OK",
"new_bugs": "12",
"projects": "3",
"reliability_rating": "1.0",
"sqale_rating": "1.0",
}
}
/>
</div>
`;

exports[`should display configure analysis button for logged in user 1`] = `
<div
className="boxed-group project-card big-padded display-flex-column display-flex-space-between"
data-key="foo"
style={
Object {
"height": 100,
}
}
>
<div>
<div
className="project-card-header"
>
<h2
className="project-card-name"
>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "foo",
},
}
}
>
Foo
</Link>
</h2>
<div
className="project-card-header-right"
>
<PrivacyBadgeContainer
className="spacer-left"
qualifier="TRK"
visibility="public"
/>
</div>
</div>
</div>
<div
className="project-card-not-analyzed"
>
<span
className="note"
>
projects.not_analyzed.TRK
</span>
<Link
className="button spacer-left"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "foo",
},
}
}
>
projects.configure_analysis
</Link>
</div>
</div>
`;

exports[`should display correclty when project need issue synch 1`] = `
<div
className="boxed-group project-card big-padded display-flex-column display-flex-space-between need-issue-sync"
data-key="foo"
style={
Object {
"height": 100,
}
}
>
<div>
<div
className="project-card-header"
>
<h2
className="project-card-name"
>
Foo
</h2>
<ProjectCardQualityGate
status="OK"
/>
<div
className="project-card-header-right"
>
<PrivacyBadgeContainer
className="spacer-left"
qualifier="TRK"
visibility="public"
/>
</div>
</div>
<div
className="display-flex-center project-card-dates spacer-top"
>
<DateTimeFormatter
date="2017-01-01"
>
<Component />
</DateTimeFormatter>
</div>
</div>
<ProjectCardOverallMeasures
componentQualifier="TRK"
measures={
Object {
"alert_status": "OK",
"new_bugs": "12",
"reliability_rating": "1.0",
"sqale_rating": "1.0",
}
}
/>
</div>
`;

exports[`should display not analyzed yet 1`] = `
<div
className="boxed-group project-card big-padded display-flex-column display-flex-space-between"
data-key="foo"
style={
Object {
"height": 100,
}
}
>
<div>
<div
className="project-card-header"
>
<h2
className="project-card-name"
>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "foo",
},
}
}
>
Foo
</Link>
</h2>
<div
className="project-card-header-right"
>
<PrivacyBadgeContainer
className="spacer-left"
qualifier="TRK"
visibility="public"
/>
</div>
</div>
</div>
<div
className="project-card-not-analyzed"
>
<span
className="note"
>
projects.not_analyzed.TRK
</span>
</div>
</div>
`;

exports[`should display the overall measures and quality gate 1`] = `
<div
className="boxed-group project-card big-padded display-flex-column display-flex-space-between"
data-key="foo"
style={
Object {
"height": 100,
}
}
>
<div>
<div
className="project-card-header"
>
<h2
className="project-card-name"
>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "foo",
},
}
}
>
Foo
</Link>
</h2>
<ProjectCardQualityGate
status="OK"
/>
<div
className="project-card-header-right"
>
<PrivacyBadgeContainer
className="spacer-left"
qualifier="TRK"
visibility="public"
/>
</div>
</div>
<div
className="display-flex-center project-card-dates spacer-top"
>
<DateTimeFormatter
date="2017-01-01"
>
<Component />
</DateTimeFormatter>
</div>
</div>
<ProjectCardOverallMeasures
componentQualifier="TRK"
measures={
Object {
"alert_status": "OK",
"new_bugs": "12",
"reliability_rating": "1.0",
"sqale_rating": "1.0",
}
}
/>
</div>
`;

+ 0
- 41
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap Vedi File

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

exports[`handles unknown languages 1`] = `
<div>
<Tooltip>
<span>
cpp, Java
</span>
</Tooltip>
</div>
`;

exports[`handles unknown languages 2`] = `
<div>
<Tooltip>
<span>
unknown, Java
</span>
</Tooltip>
</div>
`;

exports[`renders 1`] = `
<div>
<Tooltip>
<span>
Java, JavaScript
</span>
</Tooltip>
</div>
`;

exports[`sorts languages 1`] = `
<div>
<Tooltip>
<span>
JavaScript, Java
</span>
</Tooltip>
</div>
`;

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

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

exports[`should render correctly with all data 1`] = `
<div
className="project-card-leak-measures"
>
<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"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<Measure
metricKey="new_coverage"
metricType="PERCENT"
value="26.55"
/>
</div>
<div
className="project-card-measure-label"
>
metric.coverage.name
</div>
</div>
</div>
<div
className="project-card-measure"
data-key="new_duplicated_lines_density"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<Measure
metricKey="new_duplicated_lines_density"
metricType="PERCENT"
value="0.55"
/>
</div>
<div
className="project-card-measure-label"
>
metric.duplicated_lines_density.short_name
</div>
</div>
</div>
<div
className="project-card-measure project-card-ncloc"
data-key="new_lines"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<Measure
metricKey="new_lines"
metricType="SHORT_INT"
value="87"
/>
</div>
<div
className="project-card-measure-label"
>
metric.lines.name
</div>
</div>
</div>
</div>
`;

exports[`should render no data style new coverage, new duplications and new lines 1`] = `
<div
className="project-card-leak-measures"
>
<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"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<Measure
metricKey="new_duplicated_lines_density"
metricType="PERCENT"
/>
</div>
<div
className="project-card-measure-label"
>
metric.duplicated_lines_density.short_name
</div>
</div>
</div>
<div
className="project-card-measure project-card-ncloc"
data-key="new_lines"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<Measure
metricKey="new_lines"
metricType="SHORT_INT"
/>
</div>
<div
className="project-card-measure-label"
>
metric.lines.name
</div>
</div>
</div>
</div>
`;

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

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

exports[`should render correctly with all data 1`] = `
<div
className="project-card-measures"
>
<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"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<span
className="spacer-right"
>
<CoverageRating
value="88.3"
/>
</span>
<Measure
metricKey="coverage"
metricType="PERCENT"
value="88.3"
/>
</div>
<div
className="project-card-measure-label"
>
metric.coverage.name
</div>
</div>
</div>
<div
className="project-card-measure"
data-key="duplicated_lines_density"
>
<div
className="project-card-measure-inner"
>
<div
className="project-card-measure-number"
>
<span
className="spacer-right"
>
<DuplicationsRating
value={9.8}
/>
</span>
<Measure
metricKey="duplicated_lines_density"
metricType="PERCENT"
value="9.8"
/>
</div>
<div
className="project-card-measure-label"
>
metric.duplicated_lines_density.short_name
</div>
</div>
</div>
<div
className="project-card-measure project-card-ncloc"
data-key="ncloc"
>
<div
className="project-card-measure-inner pull-right"
>
<div
className="project-card-measure-number"
>
<Measure
metricKey="ncloc"
metricType="SHORT_INT"
value="2053"
/>
<span
className="spacer-left"
>
<SizeRating
value={2053}
/>
</span>
</div>
<div
className="project-card-measure-label"
>
<Connect(ProjectCardLanguages)
className="project-card-languages"
/>
</div>
</div>
</div>
</div>
`;

exports[`should render empty: application 1`] = `
<div
className="note big-spacer-top"
>
portfolio.app.empty
</div>
`;

exports[`should render empty: project 1`] = `
<div
className="note big-spacer-top"
>
overview.project.main_branch_empty
</div>
`;

exports[`should render ncloc correctly 1`] = `
<div
className="project-card-measure project-card-ncloc"
data-key="ncloc"
>
<div
className="project-card-measure-inner pull-right"
>
<div
className="project-card-measure-number"
>
<Measure
metricKey="ncloc"
metricType="SHORT_INT"
value="16549887"
/>
<span
className="spacer-left"
>
<SizeRating
value={16549887}
/>
</span>
</div>
<div
className="project-card-measure-label"
>
<Connect(ProjectCardLanguages)
className="project-card-languages"
/>
</div>
</div>
</div>
`;

+ 0
- 21
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardQualityGate-test.tsx.snap Vedi File

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

exports[`renders 1`] = `
<div
className="project-card-quality-gate big-spacer-left"
>
<Tooltip
overlay="overview.quality_gate_x.ERROR"
>
<div
className="project-card-measure-inner"
>
<Level
aria-label="quality_gates.status"
level="ERROR"
small={true}
/>
</div>
</Tooltip>
</div>
`;

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

@@ -1,69 +0,0 @@
// 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>
`;

+ 3
- 3
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap Vedi File

@@ -14,7 +14,7 @@ exports[`renders correctly: list element 1`] = `
overscanIndicesGetter={[Function]}
overscanRowCount={2}
rowCount={2}
rowHeight={163}
rowHeight={165}
rowRenderer={[Function]}
scrollToAlignment="auto"
scrollToIndex={-1}
@@ -34,7 +34,7 @@ exports[`renders correctly: row element 1`] = `
role="row"
style={
Object {
"height": 143,
"height": 145,
}
}
>
@@ -47,7 +47,7 @@ exports[`renders correctly: row element 1`] = `
"isLoggedIn": true,
}
}
height={143}
height={145}
project={
Object {
"key": "foo",

+ 70
- 0
server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.css Vedi File

@@ -0,0 +1,70 @@
/*
* 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.
*/

.project-card-main {
flex: 1 1 auto;
overflow: hidden;
}

.project-card-meta {
flex: 0 0 170px;
overflow: hidden;
background-color: rgba(230, 230, 230, 0.25);
height: 100%;
box-sizing: border-box;
}

.project-card-meta .tags-list span {
display: inline;
}

.project-card-measure-value-line {
height: calc(3 * var(--gridSize));
}

@media (max-width: 1320px) {
.project-card-measure-secondary-info {
display: none;
}
}

.project-card-leak {
background-color: var(--leakPrimaryColor);
}

.project-card-disabled *:not(g):not(path) {
color: var(--disableGrayText);
}

.project-card-disabled .rating,
.project-card-disabled .size-rating,
.project-card-disabled .duplications-rating:after,
.project-card-disabled .level {
background-color: var(--disableGrayBg);
}

.project-card-disabled .duplications-rating {
border-color: var(--disableGrayBg);
}

.project-card-disabled .project-card-main *:not(.favorite-link) svg path,
.project-card-disabled .project-card-meta path {
fill: var(--disableGrayBg) !important;
}

+ 242
- 0
server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx Vedi File

@@ -0,0 +1,242 @@
/*
* 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 classNames from 'classnames';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
import Tooltip from 'sonar-ui-common/components/controls/Tooltip';
import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon';
import DateFromNow from 'sonar-ui-common/components/intl/DateFromNow';
import DateTimeFormatter from 'sonar-ui-common/components/intl/DateTimeFormatter';
import SizeRating from 'sonar-ui-common/components/ui/SizeRating';
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
import PrivacyBadgeContainer from '../../../../components/common/PrivacyBadgeContainer';
import Favorite from '../../../../components/controls/Favorite';
import Measure from '../../../../components/measure/Measure';
import TagsList from '../../../../components/tags/TagsList';
import { getProjectUrl } from '../../../../helpers/urls';
import { isLoggedIn } from '../../../../helpers/users';
import { ComponentQualifier } from '../../../../types/component';
import { MetricKey } from '../../../../types/metrics';
import { Project } from '../../types';
import './ProjectCard.css';
import ProjectCardLanguagesContainer from './ProjectCardLanguagesContainer';
import ProjectCardMeasure from './ProjectCardMeasure';
import ProjectCardMeasures from './ProjectCardMeasures';
import ProjectCardQualityGate from './ProjectCardQualityGate';

interface Props {
currentUser: T.CurrentUser;
handleFavorite: (component: string, isFavorite: boolean) => void;
height: number;
project: Project;
type?: string;
}

function renderFirstLine(props: Props) {
const {
project: {
analysisDate,
tags,
qualifier,
isFavorite,
key,
name,
measures,
needIssueSync,
visibility
}
} = props;

return (
<div className="display-flex-center">
<div className="project-card-main big-padded padded-bottom display-flex-center">
{isFavorite !== undefined && (
<Favorite
className="spacer-right"
component={key}
favorite={isFavorite}
handleFavorite={props.handleFavorite}
qualifier={qualifier}
/>
)}
{qualifier === ComponentQualifier.Application && (
<Tooltip
placement="top"
overlay={
<span>
{translate('qualifier.APP')}
{measures.projects && (
<span>
{' ‒ '}
{translateWithParameters('x_projects_', measures.projects)}
</span>
)}
</span>
}>
<span className="spacer-right">
<QualifierIcon qualifier={qualifier} />
</span>
</Tooltip>
)}
<h2 className="project-card-name text-ellipsis" title={name}>
{needIssueSync ? name : <Link to={getProjectUrl(key)}>{name}</Link>}
</h2>

{analysisDate && (
<>
<ProjectCardQualityGate status={measures[MetricKey.alert_status]} />
<span className="flex-grow" />
<DateTimeFormatter date={analysisDate}>
{formattedAnalysisDate => (
<span className="note big-spacer-left text-ellipsis" title={formattedAnalysisDate}>
<FormattedMessage
id="projects.last_analysis_on_x"
defaultMessage={translate('projects.last_analysis_on_x')}
values={{
date: <DateFromNow date={analysisDate} />
}}
/>
</span>
)}
</DateTimeFormatter>
</>
)}
</div>
<div className="project-card-meta big-padded padded-bottom display-flex-center">
<div className="display-flex-center overflow-hidden">
<PrivacyBadgeContainer
className="spacer-right"
qualifier={qualifier}
visibility={visibility}
/>
{tags.length > 0 && <TagsList className="text-ellipsis" tags={tags} />}
</div>
</div>
</div>
);
}

function renderSecondLine(props: Props, isNewCode: boolean) {
const {
project: { measures }
} = props;

return (
<div
className={classNames('display-flex-end flex-grow', {
'project-card-leak': isNewCode
})}>
<div className="project-card-main big-padded-left big-padded-right big-padded-bottom">
{renderMeasures(props, isNewCode)}
</div>
<div className="project-card-meta display-flex-end big-padded-left big-padded-right big-padded-bottom">
{isNewCode
? measures[MetricKey.new_lines] != null && (
<ProjectCardMeasure
metricKey={MetricKey.new_lines}
label={translate('metric.lines.name')}>
<Measure
className="big"
metricKey={MetricKey.new_lines}
metricType="SHORT_INT"
value={measures[MetricKey.new_lines]}
/>
</ProjectCardMeasure>
)
: measures[MetricKey.ncloc] != null && (
<ProjectCardMeasure
metricKey={MetricKey.ncloc}
label={translate('metric.lines.name')}>
<div className="display-flex-center">
<Measure
className="big"
metricKey={MetricKey.ncloc}
metricType="SHORT_INT"
value={measures[MetricKey.ncloc]}
/>
<span className="spacer-left">
<SizeRating value={Number(measures[MetricKey.ncloc])} />
</span>
<ProjectCardLanguagesContainer
className="small spacer-left text-ellipsis"
distribution={measures[MetricKey.ncloc_language_distribution]}
/>
</div>
</ProjectCardMeasure>
)}
</div>
</div>
);
}

function renderMeasures(props: Props, isNewCode: boolean) {
const {
currentUser,
project: { measures, analysisDate, leakPeriodDate, qualifier, key }
} = props;

if (analysisDate && (!isNewCode || leakPeriodDate)) {
return (
<ProjectCardMeasures
measures={measures}
componentQualifier={qualifier}
isNewCode={isNewCode}
newCodeStartingDate={leakPeriodDate}
/>
);
} else {
return (
<div className="spacer-top spacer-bottom">
<span className="note">
{isNewCode && analysisDate
? translate('projects.no_new_code_period', qualifier)
: translate('projects.not_analyzed', qualifier)}
</span>
{qualifier !== ComponentQualifier.Application && !analysisDate && isLoggedIn(currentUser) && (
<Link className="button spacer-left" to={getProjectUrl(key)}>
{translate('projects.configure_analysis')}
</Link>
)}
</div>
);
}
}

export default function ProjectCard(props: Props) {
const {
height,
type,
project: { needIssueSync, key }
} = props;
const isNewCode = type === 'leak';

return (
<div
className={classNames('display-flex-column boxed-group it_project_card', {
'project-card-disabled': needIssueSync
})}
data-key={key}
style={{ height }}>
{renderFirstLine(props)}
{renderSecondLine(props, isNewCode)}
</div>
);
}

server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx → server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardLanguages.tsx Vedi File

@@ -19,7 +19,6 @@
*/
import { sortBy } from 'lodash';
import * as React from 'react';
import Tooltip from 'sonar-ui-common/components/controls/Tooltip';
import { translate } from 'sonar-ui-common/helpers/l10n';

interface Props {
@@ -38,29 +37,12 @@ export default function ProjectCardLanguages({ className, distribution, language
getLanguageName(languages, l[0])
);

const languagesText =
finalLanguages.slice(0, 2).join(', ') + (finalLanguages.length > 2 ? ', ...' : '');

let tooltip;
if (finalLanguages.length > 2) {
tooltip = (
<span>
{finalLanguages.map(language => (
<span key={language}>
{language}
<br />
</span>
))}
</span>
);
}
const languagesText = finalLanguages.join(', ');

return (
<div className={className}>
<Tooltip overlay={tooltip}>
<span>{languagesText}</span>
</Tooltip>
</div>
<span className={className} title={languagesText}>
{languagesText}
</span>
);
}


server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguagesContainer.tsx → server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardLanguagesContainer.tsx Vedi File

@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { connect } from 'react-redux';
import { getLanguages, Store } from '../../../store/rootReducer';
import { getLanguages, Store } from '../../../../store/rootReducer';
import ProjectCardLanguages from './ProjectCardLanguages';

const stateToProps = (state: Store) => ({

+ 55
- 0
server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasure.tsx Vedi File

@@ -0,0 +1,55 @@
/*
* 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 classNames from 'classnames';
import * as React from 'react';
import IssueTypeIcon from 'sonar-ui-common/components/icons/IssueTypeIcon';

export interface ProjectCardMeasureProps {
iconKey?: string;
label: string;
metricKey: string;
className?: string;
}

export default function ProjectCardMeasure(
props: React.PropsWithChildren<ProjectCardMeasureProps>
) {
const { iconKey, label, metricKey, children, className } = props;

const icon = <IssueTypeIcon className="spacer-right" query={iconKey || metricKey} />;

return (
<div
data-key={metricKey}
className={classNames(
'display-flex-column overflow-hidden it__project_card_measure',
className
)}>
<div className="spacer-bottom display-flex-center" title={label}>
{icon}
<span className="text-ellipsis">{label}</span>
</div>
<div className="flex-grow display-flex-center project-card-measure-value-line overflow-hidden">
<span className="invisible">{icon}</span>
<span className="text-ellipsis">{children}</span>
</div>
</div>
);
}

+ 201
- 0
server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasures.tsx Vedi File

@@ -0,0 +1,201 @@
/*
* 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 classNames from 'classnames';
import * as difference from 'date-fns/difference_in_milliseconds';
import * as React from 'react';
import DateTimeFormatter from 'sonar-ui-common/components/intl/DateTimeFormatter';
import DuplicationsRating from 'sonar-ui-common/components/ui/DuplicationsRating';
import Rating from 'sonar-ui-common/components/ui/Rating';
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
import { isDefined } from 'sonar-ui-common/helpers/types';
import Measure from '../../../../components/measure/Measure';
import CoverageRating from '../../../../components/ui/CoverageRating';
import { ComponentQualifier } from '../../../../types/component';
import { MetricKey } from '../../../../types/metrics';
import { formatDuration } from '../../utils';
import ProjectCardMeasure from './ProjectCardMeasure';

export interface ProjectCardMeasuresProps {
isNewCode: boolean;
measures: T.Dict<string | undefined>;
componentQualifier: ComponentQualifier;
newCodeStartingDate?: string;
}

function renderCoverage(props: ProjectCardMeasuresProps) {
const { measures, isNewCode } = props;
const coverageMetric = isNewCode ? MetricKey.new_coverage : MetricKey.coverage;

return (
<ProjectCardMeasure metricKey={coverageMetric} label={translate('metric.coverage.name')}>
<div className="display-flex-center">
<Measure
className="big"
metricKey={coverageMetric}
metricType="PERCENT"
value={measures[coverageMetric]}
/>
{measures[coverageMetric] && (
<span className="spacer-left project-card-measure-secondary-info">
<CoverageRating value={measures[coverageMetric]} />
</span>
)}
</div>
</ProjectCardMeasure>
);
}

function renderDuplication(props: ProjectCardMeasuresProps) {
const { measures, isNewCode } = props;
const duplicationMetric = isNewCode
? MetricKey.new_duplicated_lines_density
: MetricKey.duplicated_lines_density;

return (
<ProjectCardMeasure
metricKey={duplicationMetric}
label={translate('metric.duplicated_lines_density.short_name')}>
<div className="display-flex-center">
<Measure
className="big"
metricKey={duplicationMetric}
metricType="PERCENT"
value={measures[duplicationMetric]}
/>
{measures[duplicationMetric] != null && (
<span className="spacer-left project-card-measure-secondary-info">
<DuplicationsRating value={Number(measures[duplicationMetric])} />
</span>
)}
</div>
</ProjectCardMeasure>
);
}

function renderRatings(props: ProjectCardMeasuresProps) {
const { isNewCode, measures } = props;

const measureList = [
{
iconLabel: translate('metric.bugs.name'),
noShrink: true,
metricKey: isNewCode ? MetricKey.new_bugs : MetricKey.bugs,
metricRatingKey: isNewCode ? MetricKey.new_reliability_rating : MetricKey.reliability_rating,
metricType: 'SHORT_INT'
},
{
iconLabel: translate('metric.vulnerabilities.name'),
metricKey: isNewCode ? MetricKey.new_vulnerabilities : MetricKey.vulnerabilities,
metricRatingKey: isNewCode ? MetricKey.new_security_rating : MetricKey.security_rating,
metricType: 'SHORT_INT'
},
{
iconKey: 'security_hotspots',
iconLabel: translate('projects.security_hotspots_reviewed'),
metricKey: isNewCode
? MetricKey.new_security_hotspots_reviewed
: MetricKey.security_hotspots_reviewed,
metricRatingKey: isNewCode
? MetricKey.new_security_review_rating
: MetricKey.security_review_rating,
metricType: 'PERCENT'
},
{
iconLabel: translate('metric.code_smells.name'),
metricKey: isNewCode ? MetricKey.new_code_smells : MetricKey.code_smells,
metricRatingKey: isNewCode ? MetricKey.new_maintainability_rating : MetricKey.sqale_rating,
metricType: 'SHORT_INT'
}
];

return measureList.map(measure => {
const { iconKey, iconLabel, metricKey, metricRatingKey, metricType, noShrink } = measure;

return (
<ProjectCardMeasure
className={classNames({ 'flex-0': noShrink })}
key={metricKey}
metricKey={metricKey}
iconKey={iconKey}
label={iconLabel}>
<Measure
className="spacer-right big project-card-measure-secondary-info"
metricKey={metricKey}
metricType={metricType}
value={measures[metricKey]}
/>
<span className="big">
<Rating value={measures[metricRatingKey]} />
</span>
</ProjectCardMeasure>
);
});
}

export default function ProjectCardMeasures(props: ProjectCardMeasuresProps) {
const { isNewCode, measures, componentQualifier, newCodeStartingDate } = props;

const { ncloc } = measures;

if (!isNewCode && !ncloc) {
return (
<div className="note big-spacer-top">
{componentQualifier === ComponentQualifier.Application
? translate('portfolio.app.empty')
: translate('overview.project.main_branch_empty')}
</div>
);
}

const newCodeTimespan = newCodeStartingDate ? difference(Date.now(), newCodeStartingDate) : 0;

const measureList = [
...renderRatings(props),
renderCoverage(props),
renderDuplication(props)
].filter(isDefined);

return (
<>
{isNewCode && newCodeTimespan !== undefined && newCodeStartingDate && (
<DateTimeFormatter date={newCodeStartingDate}>
{formattedNewCodeStartingDate => (
<p className="spacer-top spacer-bottom" title={formattedNewCodeStartingDate}>
{translateWithParameters(
'projects.new_code_period_x',
formatDuration(newCodeTimespan)
)}
</p>
)}
</DateTimeFormatter>
)}
<div className="display-flex-row display-flex-space-between">
{measureList.map((measure, i) => (
// eslint-disable-next-line react/no-array-index-key
<React.Fragment key={i}>
{i > 0 && <span className="bordered-left little-spacer" />}
{measure}
</React.Fragment>
))}
</div>
</>
);
}

server/sonar-web/src/main/js/apps/projects/components/ProjectCardQualityGate.tsx → server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardQualityGate.tsx Vedi File

@@ -19,9 +19,8 @@
*/
import * as React from 'react';
import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip';
import Tooltip from 'sonar-ui-common/components/controls/Tooltip';
import Level from 'sonar-ui-common/components/ui/Level';
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { formatMeasure } from 'sonar-ui-common/helpers/measures';

interface Props {
@@ -33,24 +32,17 @@ export default function ProjectCardQualityGate({ status }: Props) {
return null;
}

const tooltip = translateWithParameters(
'overview.quality_gate_x',
formatMeasure(status, 'LEVEL')
);
const title = `${translate('quality_gates.status')}: ${formatMeasure(status, 'LEVEL')}`;

return (
<div className="project-card-quality-gate big-spacer-left">
<Tooltip overlay={tooltip}>
<div className="project-card-measure-inner">
<Level aria-label={translate('quality_gates.status')} level={status} small={true} />
{status === 'WARN' && (
<HelpTooltip
className="little-spacer-left"
overlay={translate('quality_gates.conditions.warning.tooltip')}
/>
)}
</div>
</Tooltip>
<div className="big-spacer-left" title={title}>
<Level aria-label={title} level={status} small={true} />
{status === 'WARN' && (
<HelpTooltip
className="little-spacer-left"
overlay={translate('quality_gates.conditions.warning.tooltip')}
/>
)}
</div>
);
}

server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCard-test.tsx → server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCard-test.tsx Vedi File

@@ -19,16 +19,13 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import PrivacyBadgeContainer from '../../../../components/common/PrivacyBadgeContainer';
import { mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks';
import { ComponentQualifier } from '../../../../types/component';
import { Project } from '../../types';
import PrivacyBadgeContainer from '../../../../../components/common/PrivacyBadgeContainer';
import TagsList from '../../../../../components/tags/TagsList';
import { mockCurrentUser, mockLoggedInUser } from '../../../../../helpers/testMocks';
import { ComponentQualifier } from '../../../../../types/component';
import { Project } from '../../../types';
import ProjectCard from '../ProjectCard';

jest.mock(
'date-fns/difference_in_milliseconds',
() => () => 1000 * 60 * 60 * 24 * 30 * 8 // ~ 8 months
);
import ProjectCardQualityGate from '../ProjectCardQualityGate';

const MEASURES = {
alert_status: 'OK',
@@ -55,24 +52,11 @@ it('should display correclty when project need issue synch', () => {
expect(shallowRender({ ...PROJECT, needIssueSync: true })).toMatchSnapshot();
});

it('should display analysis date (and not leak period) when defined', () => {
expect(
shallowRender(PROJECT)
.find('.project-card-dates')
.exists()
).toBe(true);
expect(
shallowRender({ ...PROJECT, analysisDate: undefined })
.find('.project-card-dates')
.exists()
).toBe(false);
});

it('should not display the quality gate', () => {
const project = { ...PROJECT, analysisDate: undefined };
expect(
shallowRender(project)
.find('ProjectCardOverallQualityGate')
.find(ProjectCardQualityGate)
.exists()
).toBe(false);
});
@@ -81,7 +65,7 @@ it('should display tags', () => {
const project = { ...PROJECT, tags: ['foo', 'bar'] };
expect(
shallowRender(project)
.find('TagsList')
.find(TagsList)
.exists()
).toBe(true);
});
@@ -120,8 +104,14 @@ it('should display applications', () => {
).toMatchSnapshot('with project count');
});

function shallowRender(project: Project, user: T.CurrentUser = USER_LOGGED_OUT) {
function shallowRender(project: Project, user: T.CurrentUser = USER_LOGGED_OUT, type?: string) {
return shallow(
<ProjectCard currentUser={user} handleFavorite={jest.fn()} height={100} project={project} />
<ProjectCard
currentUser={user}
handleFavorite={jest.fn()}
height={100}
project={project}
type={type}
/>
);
}

server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLanguages-test.tsx → server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardLanguages-test.tsx Vedi File


server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganizationContainer.tsx → server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardMeasure-test.tsx Vedi File

@@ -17,12 +17,18 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { connect } from 'react-redux';
import { areThereCustomOrganizations, Store } from '../../../store/rootReducer';
import ProjectCardOrganization from './ProjectCardOrganization';

const stateToProps = (state: Store) => ({
organizationsEnabled: areThereCustomOrganizations(state)
import { shallow } from 'enzyme';
import * as React from 'react';
import ProjectCardMeasure, { ProjectCardMeasureProps } from '../ProjectCardMeasure';

it('should render correctly', () => {
const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot();
});

export default connect(stateToProps)(ProjectCardOrganization);
function shallowRender(props: Partial<ProjectCardMeasureProps> = {}) {
return shallow<ProjectCardMeasureProps>(
<ProjectCardMeasure metricKey="test-metric-key" label="test-label" {...props} />
);
}

+ 99
- 0
server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardMeasures-test.tsx Vedi File

@@ -0,0 +1,99 @@
/*
* 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 CoverageRating from '../../../../../components/ui/CoverageRating';
import { ComponentQualifier } from '../../../../../types/component';
import { MetricKey } from '../../../../../types/metrics';
import ProjectCardMeasures, { ProjectCardMeasuresProps } from '../ProjectCardMeasures';

jest.mock(
'date-fns/difference_in_milliseconds',
() => () => 1000 * 60 * 60 * 24 * 30 * 8 // ~ 8 months
);

describe('Overall measures', () => {
it('should be rendered properly', () => {
const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot();
});

it("should be not be rendered if there's no line of code", () => {
let wrapper = shallowRender({ [MetricKey.ncloc]: undefined });
expect(wrapper).toMatchSnapshot('project');

wrapper = shallowRender(
{ ncloc: undefined },
{ componentQualifier: ComponentQualifier.Application }
);
expect(wrapper).toMatchSnapshot('application');
});

it('should not render coverage graph if there is no value for it', () => {
const wrapper = shallowRender({ [MetricKey.coverage]: undefined });
expect(wrapper.find(CoverageRating).exists()).toBe(false);
});
});

describe('New code measures', () => {
it('should be rendered properly', () => {
const wrapper = shallowRender({}, { isNewCode: true });
expect(wrapper).toMatchSnapshot();
});
});

function shallowRender(
measuresOverride: T.Dict<string | undefined> = {},
props: Partial<ProjectCardMeasuresProps> = {}
) {
const measures = {
[MetricKey.alert_status]: 'ERROR',
[MetricKey.bugs]: '17',
[MetricKey.code_smells]: '132',
[MetricKey.coverage]: '88.3',
[MetricKey.duplicated_lines_density]: '9.8',
[MetricKey.ncloc]: '2053',
[MetricKey.reliability_rating]: '1.0',
[MetricKey.security_rating]: '1.0',
[MetricKey.sqale_rating]: '1.0',
[MetricKey.vulnerabilities]: '0',
[MetricKey.new_reliability_rating]: '1.0',
[MetricKey.new_bugs]: '8',
[MetricKey.new_security_rating]: '2.0',
[MetricKey.new_vulnerabilities]: '2',
[MetricKey.new_maintainability_rating]: '1.0',
[MetricKey.new_code_smells]: '0',
[MetricKey.new_coverage]: '26.55',
[MetricKey.new_duplicated_lines_density]: '0.55',
[MetricKey.new_lines]: '87',
...measuresOverride
};

return shallow<ProjectCardMeasuresProps>(
<ProjectCardMeasures
componentQualifier={ComponentQualifier.Project}
isNewCode={false}
measures={measures}
newCodeStartingDate="2018-01-01"
{...props}
/>
);
}

server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardQualityGate-test.tsx → server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardQualityGate-test.tsx Vedi File


+ 540
- 0
server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCard-test.tsx.snap Vedi File

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

exports[`should display applications 1`] = `
<div
className="display-flex-column boxed-group it_project_card"
data-key="foo"
style={
Object {
"height": 100,
}
}
>
<div
className="display-flex-center"
>
<div
className="project-card-main big-padded padded-bottom display-flex-center"
>
<Tooltip
overlay={
<span>
qualifier.APP
</span>
}
placement="top"
>
<span
className="spacer-right"
>
<QualifierIcon
qualifier="APP"
/>
</span>
</Tooltip>
<h2
className="project-card-name text-ellipsis"
title="Foo"
>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "foo",
},
}
}
>
Foo
</Link>
</h2>
<ProjectCardQualityGate
status="OK"
/>
<span
className="flex-grow"
/>
<DateTimeFormatter
date="2017-01-01"
>
<Component />
</DateTimeFormatter>
</div>
<div
className="project-card-meta big-padded padded-bottom display-flex-center"
>
<div
className="display-flex-center overflow-hidden"
>
<PrivacyBadgeContainer
className="spacer-right"
qualifier="APP"
visibility="public"
/>
</div>
</div>
</div>
<div
className="display-flex-end flex-grow"
>
<div
className="project-card-main big-padded-left big-padded-right big-padded-bottom"
>
<ProjectCardMeasures
componentQualifier="APP"
isNewCode={false}
measures={
Object {
"alert_status": "OK",
"new_bugs": "12",
"reliability_rating": "1.0",
"sqale_rating": "1.0",
}
}
/>
</div>
<div
className="project-card-meta display-flex-end big-padded-left big-padded-right big-padded-bottom"
/>
</div>
</div>
`;

exports[`should display applications: with project count 1`] = `
<div
className="display-flex-column boxed-group it_project_card"
data-key="foo"
style={
Object {
"height": 100,
}
}
>
<div
className="display-flex-center"
>
<div
className="project-card-main big-padded padded-bottom display-flex-center"
>
<Tooltip
overlay={
<span>
qualifier.APP
<span>
x_projects_.3
</span>
</span>
}
placement="top"
>
<span
className="spacer-right"
>
<QualifierIcon
qualifier="APP"
/>
</span>
</Tooltip>
<h2
className="project-card-name text-ellipsis"
title="Foo"
>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "foo",
},
}
}
>
Foo
</Link>
</h2>
<ProjectCardQualityGate
status="OK"
/>
<span
className="flex-grow"
/>
<DateTimeFormatter
date="2017-01-01"
>
<Component />
</DateTimeFormatter>
</div>
<div
className="project-card-meta big-padded padded-bottom display-flex-center"
>
<div
className="display-flex-center overflow-hidden"
>
<PrivacyBadgeContainer
className="spacer-right"
qualifier="APP"
visibility="public"
/>
</div>
</div>
</div>
<div
className="display-flex-end flex-grow"
>
<div
className="project-card-main big-padded-left big-padded-right big-padded-bottom"
>
<ProjectCardMeasures
componentQualifier="APP"
isNewCode={false}
measures={
Object {
"alert_status": "OK",
"new_bugs": "12",
"projects": "3",
"reliability_rating": "1.0",
"sqale_rating": "1.0",
}
}
/>
</div>
<div
className="project-card-meta display-flex-end big-padded-left big-padded-right big-padded-bottom"
/>
</div>
</div>
`;

exports[`should display configure analysis button for logged in user 1`] = `
<div
className="display-flex-column boxed-group it_project_card"
data-key="foo"
style={
Object {
"height": 100,
}
}
>
<div
className="display-flex-center"
>
<div
className="project-card-main big-padded padded-bottom display-flex-center"
>
<h2
className="project-card-name text-ellipsis"
title="Foo"
>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "foo",
},
}
}
>
Foo
</Link>
</h2>
</div>
<div
className="project-card-meta big-padded padded-bottom display-flex-center"
>
<div
className="display-flex-center overflow-hidden"
>
<PrivacyBadgeContainer
className="spacer-right"
qualifier="TRK"
visibility="public"
/>
</div>
</div>
</div>
<div
className="display-flex-end flex-grow"
>
<div
className="project-card-main big-padded-left big-padded-right big-padded-bottom"
>
<div
className="spacer-top spacer-bottom"
>
<span
className="note"
>
projects.not_analyzed.TRK
</span>
<Link
className="button spacer-left"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "foo",
},
}
}
>
projects.configure_analysis
</Link>
</div>
</div>
<div
className="project-card-meta display-flex-end big-padded-left big-padded-right big-padded-bottom"
/>
</div>
</div>
`;

exports[`should display correclty when project need issue synch 1`] = `
<div
className="display-flex-column boxed-group it_project_card project-card-disabled"
data-key="foo"
style={
Object {
"height": 100,
}
}
>
<div
className="display-flex-center"
>
<div
className="project-card-main big-padded padded-bottom display-flex-center"
>
<h2
className="project-card-name text-ellipsis"
title="Foo"
>
Foo
</h2>
<ProjectCardQualityGate
status="OK"
/>
<span
className="flex-grow"
/>
<DateTimeFormatter
date="2017-01-01"
>
<Component />
</DateTimeFormatter>
</div>
<div
className="project-card-meta big-padded padded-bottom display-flex-center"
>
<div
className="display-flex-center overflow-hidden"
>
<PrivacyBadgeContainer
className="spacer-right"
qualifier="TRK"
visibility="public"
/>
</div>
</div>
</div>
<div
className="display-flex-end flex-grow"
>
<div
className="project-card-main big-padded-left big-padded-right big-padded-bottom"
>
<ProjectCardMeasures
componentQualifier="TRK"
isNewCode={false}
measures={
Object {
"alert_status": "OK",
"new_bugs": "12",
"reliability_rating": "1.0",
"sqale_rating": "1.0",
}
}
/>
</div>
<div
className="project-card-meta display-flex-end big-padded-left big-padded-right big-padded-bottom"
/>
</div>
</div>
`;

exports[`should display not analyzed yet 1`] = `
<div
className="display-flex-column boxed-group it_project_card"
data-key="foo"
style={
Object {
"height": 100,
}
}
>
<div
className="display-flex-center"
>
<div
className="project-card-main big-padded padded-bottom display-flex-center"
>
<h2
className="project-card-name text-ellipsis"
title="Foo"
>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "foo",
},
}
}
>
Foo
</Link>
</h2>
</div>
<div
className="project-card-meta big-padded padded-bottom display-flex-center"
>
<div
className="display-flex-center overflow-hidden"
>
<PrivacyBadgeContainer
className="spacer-right"
qualifier="TRK"
visibility="public"
/>
</div>
</div>
</div>
<div
className="display-flex-end flex-grow"
>
<div
className="project-card-main big-padded-left big-padded-right big-padded-bottom"
>
<div
className="spacer-top spacer-bottom"
>
<span
className="note"
>
projects.not_analyzed.TRK
</span>
</div>
</div>
<div
className="project-card-meta display-flex-end big-padded-left big-padded-right big-padded-bottom"
/>
</div>
</div>
`;

exports[`should display the overall measures and quality gate 1`] = `
<div
className="display-flex-column boxed-group it_project_card"
data-key="foo"
style={
Object {
"height": 100,
}
}
>
<div
className="display-flex-center"
>
<div
className="project-card-main big-padded padded-bottom display-flex-center"
>
<h2
className="project-card-name text-ellipsis"
title="Foo"
>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "foo",
},
}
}
>
Foo
</Link>
</h2>
<ProjectCardQualityGate
status="OK"
/>
<span
className="flex-grow"
/>
<DateTimeFormatter
date="2017-01-01"
>
<Component />
</DateTimeFormatter>
</div>
<div
className="project-card-meta big-padded padded-bottom display-flex-center"
>
<div
className="display-flex-center overflow-hidden"
>
<PrivacyBadgeContainer
className="spacer-right"
qualifier="TRK"
visibility="public"
/>
</div>
</div>
</div>
<div
className="display-flex-end flex-grow"
>
<div
className="project-card-main big-padded-left big-padded-right big-padded-bottom"
>
<ProjectCardMeasures
componentQualifier="TRK"
isNewCode={false}
measures={
Object {
"alert_status": "OK",
"new_bugs": "12",
"reliability_rating": "1.0",
"sqale_rating": "1.0",
}
}
/>
</div>
<div
className="project-card-meta display-flex-end big-padded-left big-padded-right big-padded-bottom"
/>
</div>
</div>
`;

+ 33
- 0
server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap Vedi File

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

exports[`handles unknown languages 1`] = `
<span
title="cpp, Java"
>
cpp, Java
</span>
`;

exports[`handles unknown languages 2`] = `
<span
title="unknown, Java"
>
unknown, Java
</span>
`;

exports[`renders 1`] = `
<span
title="Java, JavaScript"
>
Java, JavaScript
</span>
`;

exports[`sorts languages 1`] = `
<span
title="JavaScript, Java"
>
JavaScript, Java
</span>
`;

+ 38
- 0
server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardMeasure-test.tsx.snap Vedi File

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

exports[`should render correctly 1`] = `
<div
className="display-flex-column overflow-hidden it__project_card_measure"
data-key="test-metric-key"
>
<div
className="spacer-bottom display-flex-center"
title="test-label"
>
<IssueTypeIcon
className="spacer-right"
query="test-metric-key"
/>
<span
className="text-ellipsis"
>
test-label
</span>
</div>
<div
className="flex-grow display-flex-center project-card-measure-value-line overflow-hidden"
>
<span
className="invisible"
>
<IssueTypeIcon
className="spacer-right"
query="test-metric-key"
/>
</span>
<span
className="text-ellipsis"
/>
</div>
</div>
`;

+ 314
- 0
server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardMeasures-test.tsx.snap Vedi File

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

exports[`New code measures should be rendered properly 1`] = `
<Fragment>
<DateTimeFormatter
date="2018-01-01"
>
<Component />
</DateTimeFormatter>
<div
className="display-flex-row display-flex-space-between"
>
<ProjectCardMeasure
className="flex-0"
key="new_bugs"
label="metric.bugs.name"
metricKey="new_bugs"
>
<Measure
className="spacer-right big project-card-measure-secondary-info"
metricKey="new_bugs"
metricType="SHORT_INT"
value="8"
/>
<span
className="big"
>
<Rating
value="1.0"
/>
</span>
</ProjectCardMeasure>
<span
className="bordered-left little-spacer"
/>
<ProjectCardMeasure
className=""
key="new_vulnerabilities"
label="metric.vulnerabilities.name"
metricKey="new_vulnerabilities"
>
<Measure
className="spacer-right big project-card-measure-secondary-info"
metricKey="new_vulnerabilities"
metricType="SHORT_INT"
value="2"
/>
<span
className="big"
>
<Rating
value="2.0"
/>
</span>
</ProjectCardMeasure>
<span
className="bordered-left little-spacer"
/>
<ProjectCardMeasure
className=""
iconKey="security_hotspots"
key="new_security_hotspots_reviewed"
label="projects.security_hotspots_reviewed"
metricKey="new_security_hotspots_reviewed"
>
<Measure
className="spacer-right big project-card-measure-secondary-info"
metricKey="new_security_hotspots_reviewed"
metricType="PERCENT"
/>
<span
className="big"
>
<Rating />
</span>
</ProjectCardMeasure>
<span
className="bordered-left little-spacer"
/>
<ProjectCardMeasure
className=""
key="new_code_smells"
label="metric.code_smells.name"
metricKey="new_code_smells"
>
<Measure
className="spacer-right big project-card-measure-secondary-info"
metricKey="new_code_smells"
metricType="SHORT_INT"
value="0"
/>
<span
className="big"
>
<Rating
value="1.0"
/>
</span>
</ProjectCardMeasure>
<span
className="bordered-left little-spacer"
/>
<ProjectCardMeasure
label="metric.coverage.name"
metricKey="new_coverage"
>
<div
className="display-flex-center"
>
<Measure
className="big"
metricKey="new_coverage"
metricType="PERCENT"
value="26.55"
/>
<span
className="spacer-left project-card-measure-secondary-info"
>
<CoverageRating
value="26.55"
/>
</span>
</div>
</ProjectCardMeasure>
<span
className="bordered-left little-spacer"
/>
<ProjectCardMeasure
label="metric.duplicated_lines_density.short_name"
metricKey="new_duplicated_lines_density"
>
<div
className="display-flex-center"
>
<Measure
className="big"
metricKey="new_duplicated_lines_density"
metricType="PERCENT"
value="0.55"
/>
<span
className="spacer-left project-card-measure-secondary-info"
>
<DuplicationsRating
value={0.55}
/>
</span>
</div>
</ProjectCardMeasure>
</div>
</Fragment>
`;

exports[`Overall measures should be not be rendered if there's no line of code: application 1`] = `
<div
className="note big-spacer-top"
>
portfolio.app.empty
</div>
`;

exports[`Overall measures should be not be rendered if there's no line of code: project 1`] = `
<div
className="note big-spacer-top"
>
overview.project.main_branch_empty
</div>
`;

exports[`Overall measures should be rendered properly 1`] = `
<Fragment>
<div
className="display-flex-row display-flex-space-between"
>
<ProjectCardMeasure
className="flex-0"
key="bugs"
label="metric.bugs.name"
metricKey="bugs"
>
<Measure
className="spacer-right big project-card-measure-secondary-info"
metricKey="bugs"
metricType="SHORT_INT"
value="17"
/>
<span
className="big"
>
<Rating
value="1.0"
/>
</span>
</ProjectCardMeasure>
<span
className="bordered-left little-spacer"
/>
<ProjectCardMeasure
className=""
key="vulnerabilities"
label="metric.vulnerabilities.name"
metricKey="vulnerabilities"
>
<Measure
className="spacer-right big project-card-measure-secondary-info"
metricKey="vulnerabilities"
metricType="SHORT_INT"
value="0"
/>
<span
className="big"
>
<Rating
value="1.0"
/>
</span>
</ProjectCardMeasure>
<span
className="bordered-left little-spacer"
/>
<ProjectCardMeasure
className=""
iconKey="security_hotspots"
key="security_hotspots_reviewed"
label="projects.security_hotspots_reviewed"
metricKey="security_hotspots_reviewed"
>
<Measure
className="spacer-right big project-card-measure-secondary-info"
metricKey="security_hotspots_reviewed"
metricType="PERCENT"
/>
<span
className="big"
>
<Rating />
</span>
</ProjectCardMeasure>
<span
className="bordered-left little-spacer"
/>
<ProjectCardMeasure
className=""
key="code_smells"
label="metric.code_smells.name"
metricKey="code_smells"
>
<Measure
className="spacer-right big project-card-measure-secondary-info"
metricKey="code_smells"
metricType="SHORT_INT"
value="132"
/>
<span
className="big"
>
<Rating
value="1.0"
/>
</span>
</ProjectCardMeasure>
<span
className="bordered-left little-spacer"
/>
<ProjectCardMeasure
label="metric.coverage.name"
metricKey="coverage"
>
<div
className="display-flex-center"
>
<Measure
className="big"
metricKey="coverage"
metricType="PERCENT"
value="88.3"
/>
<span
className="spacer-left project-card-measure-secondary-info"
>
<CoverageRating
value="88.3"
/>
</span>
</div>
</ProjectCardMeasure>
<span
className="bordered-left little-spacer"
/>
<ProjectCardMeasure
label="metric.duplicated_lines_density.short_name"
metricKey="duplicated_lines_density"
>
<div
className="display-flex-center"
>
<Measure
className="big"
metricKey="duplicated_lines_density"
metricType="PERCENT"
value="9.8"
/>
<span
className="spacer-left project-card-measure-secondary-info"
>
<DuplicationsRating
value={9.8}
/>
</span>
</div>
</ProjectCardMeasure>
</div>
</Fragment>
`;

+ 14
- 0
server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardQualityGate-test.tsx.snap Vedi File

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

exports[`renders 1`] = `
<div
className="big-spacer-left"
title="quality_gates.status: ERROR"
>
<Level
aria-label="quality_gates.status: ERROR"
level="ERROR"
small={true}
/>
</div>
`;

+ 0
- 163
server/sonar-web/src/main/js/apps/projects/styles.css Vedi File

@@ -55,144 +55,14 @@
margin-bottom: 0;
}

.project-card {
position: relative;
min-height: 114px;
box-sizing: border-box;
}

.project-card-header {
display: flex;
align-items: center;
}

.project-card-header-right {
margin-left: auto;
}

.project-card-name {
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.project-card-leak-date {
padding: 4px 8px;
margin: -5px -4px -5px 24px;
background-color: var(--leakPrimaryColor);
border: 1px solid var(--leakSecondaryColor);
}

.project-card-measures {
display: flex;
padding-top: 8px;
margin: 0 -15px 0 -30px;
}

.project-card-leak-measures {
display: flex;
padding: 8px 0;
margin: 4px -4px;
background-color: var(--leakPrimaryColor);
border: 1px solid var(--leakSecondaryColor);
}

.projects-leak-sorting-option {
background-color: var(--leakPrimaryColor);
margin-bottom: 2px;
}

.projects-leak-sorting-option.is-focused {
background-color: var(--leakSecondaryColor);
}

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

.project-card-measure {
position: relative;
display: inline-block;
vertical-align: top;
text-align: center;
box-sizing: border-box;
padding: 0 var(--gridSize);
}

.project-card-measure + .project-card-measure:before {
position: absolute;
top: 50%;
left: 0;
width: 0;
height: var(--controlHeight);
margin-top: -12px;
border-left: 1px solid var(--barBorderColor);
content: '';
}

.project-card-measure .level {
margin-top: 0;
margin-bottom: 0;
}

.project-card-measure-inner {
display: inline-block;
vertical-align: top;
text-align: center;
}

.project-card-measure-number {
line-height: 25px;
font-size: 18px;
white-space: nowrap;
}

.project-card-measure-label {
margin-top: 4px;
font-size: var(--smallFontSize);
}

.project-card-measure-label-with-icon {
margin-top: 2px;
font-size: var(--smallFontSize);
white-space: nowrap;
}

.project-card-ncloc {
margin-left: auto;
text-align: right;
}

.project-card-ncloc:before {
display: none;
}

.project-card-ncloc .project-card-measure-inner {
text-align: right;
}

.project-card-languages {
display: inline-block;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.project-card-quality-gate {
line-height: var(--controlHeight);
}

.project-card-not-analyzed {
padding: 14px 0;
}

.projects-facet-header {
padding: 10px;
transition: none;
}

.projects-facet-list {
padding-left: 10px;
padding-right: 10px;
@@ -208,9 +78,6 @@
float: right;
}

.projects-facets-reset .button {
}

.projects-facet-bar {
display: inline-block;
width: 60px;
@@ -295,33 +162,3 @@
padding: calc(4 * var(--gridSize)) 0;
text-align: center;
}

.need-issue-sync .rating,
.need-issue-sync .size-rating,
.need-issue-sync .duplications-rating:after,
.need-issue-sync .level {
background-color: lightgray;
}

.need-issue-sync .project-card-dates path,
.need-issue-sync .project-card-measure path,
.need-issue-sync .project-card-leak-measures path {
fill: lightgray !important;
}

.need-issue-sync .duplications-rating {
border-color: lightgray;
}

.need-issue-sync * {
color: #adadad;
}

.need-issue-sync .project-card-header .icon-outline.is-filled path {
color: rgb(237, 125, 32);
}

.need-issue-sync .rating,
.need-issue-sync .size-rating {
color: white !important;
}

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

@@ -913,7 +913,7 @@ projects.no_new_code_period.TRK=Project has no new code data yet.
projects.no_new_code_period.APP=Application has no new code data yet.
projects.new_code_period_x=New code: last {0}
projects.configure_analysis=Configure analysis
projects.last_analysis_on_x=Last analysis: {0}
projects.last_analysis_on_x=Last analysis: {date}
projects.search=Search by project name or key
projects.perspective=Perspective
projects.skip_to_filters=Skip to project filters
@@ -2736,7 +2736,6 @@ overview.failed_conditions=Failed conditions
overview.X_more_failed_conditions={0} more failed conditions
overview.X_conditions_failed={0} conditions failed
overview.quality_gate=Quality Gate Status
overview.quality_gate_x=Quality Gate: {0}
overview.quality_gate_failed_with_x=with {0} errors
overview.quality_gate_code_clean=Your code is clean!
overview.quality_gate_all_conditions_passed=All conditions passed.

Loading…
Annulla
Salva