aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMathieu Suen <mathieu.suen@sonarsource.com>2020-06-18 15:42:33 +0200
committersonartech <sonartech@sonarsource.com>2020-09-14 20:07:27 +0000
commitad4424906d27fc58f51e250532e49009a421fb87 (patch)
treeaa745e9229e4329091ad4cc3098264665b8b6670
parent17f30a2dff4ae0c087ccdcbb834b7b873b4a5f17 (diff)
downloadsonarqube-ad4424906d27fc58f51e250532e49009a421fb87.tar.gz
sonarqube-ad4424906d27fc58f51e250532e49009a421fb87.zip
SONAR-13421 Improve project cards
-rw-r--r--server/sonar-web/src/main/js/app/styles/init/misc.css12
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCard.tsx200
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx105
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganization.tsx39
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx140
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardRatingMeasure.tsx59
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx18
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeakMeasures-test.tsx53
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverallMeasures-test.tsx71
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardRatingMeasure-test.tsx47
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCard-test.tsx.snap416
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap41
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap278
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap232
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardQualityGate-test.tsx.snap21
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardRatingMeasure-test.tsx.snap69
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.css70
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx242
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardLanguages.tsx (renamed from server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx)26
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardLanguagesContainer.tsx (renamed from server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguagesContainer.tsx)2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasure.tsx55
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasures.tsx201
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardQualityGate.tsx (renamed from server/sonar-web/src/main/js/apps/projects/components/ProjectCardQualityGate.tsx)28
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCard-test.tsx (renamed from server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCard-test.tsx)42
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardLanguages-test.tsx (renamed from server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLanguages-test.tsx)0
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardMeasure-test.tsx (renamed from server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganizationContainer.tsx)18
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardMeasures-test.tsx99
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardQualityGate-test.tsx (renamed from server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardQualityGate-test.tsx)0
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCard-test.tsx.snap540
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap33
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardMeasure-test.tsx.snap38
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardMeasures-test.tsx.snap314
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardQualityGate-test.tsx.snap14
-rw-r--r--server/sonar-web/src/main/js/apps/projects/styles.css163
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties3
36 files changed, 1673 insertions, 2022 deletions
diff --git a/server/sonar-web/src/main/js/app/styles/init/misc.css b/server/sonar-web/src/main/js/app/styles/init/misc.css
index a5133b41d99..7b277f0c018 100644
--- a/server/sonar-web/src/main/js/app/styles/init/misc.css
+++ b/server/sonar-web/src/main/js/app/styles/init/misc.css
@@ -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;
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.tsx
deleted file mode 100644
index a5b7f1407e7..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.tsx
+++ /dev/null
@@ -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>
- );
-}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx
deleted file mode 100644
index 8357df3a7c2..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx
+++ /dev/null
@@ -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>
- );
-}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganization.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganization.tsx
deleted file mode 100644
index 6a653279f43..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganization.tsx
+++ /dev/null
@@ -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>
- );
-}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx
deleted file mode 100644
index 93ee98af1e6..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx
+++ /dev/null
@@ -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>
- );
-}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardRatingMeasure.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardRatingMeasure.tsx
deleted file mode 100644
index 6ad1796094b..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardRatingMeasure.tsx
+++ /dev/null
@@ -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>
- );
-}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx
index 535e3b80a09..d054555d6e8 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx
@@ -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' }}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeakMeasures-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeakMeasures-test.tsx
deleted file mode 100644
index ef39c12f927..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeakMeasures-test.tsx
+++ /dev/null
@@ -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();
-});
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverallMeasures-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverallMeasures-test.tsx
deleted file mode 100644
index 4516ca937b6..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverallMeasures-test.tsx
+++ /dev/null
@@ -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}
- />
- );
-}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardRatingMeasure-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardRatingMeasure-test.tsx
deleted file mode 100644
index 58f4dd65714..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardRatingMeasure-test.tsx
+++ /dev/null
@@ -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}
- />
- );
-}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCard-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCard-test.tsx.snap
deleted file mode 100644
index e8188017818..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCard-test.tsx.snap
+++ /dev/null
@@ -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>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap
deleted file mode 100644
index 30fdc18ecc1..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap
+++ /dev/null
@@ -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>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap
deleted file mode 100644
index cf796735609..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap
+++ /dev/null
@@ -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>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap
deleted file mode 100644
index 69a88c913e4..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap
+++ /dev/null
@@ -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>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardQualityGate-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardQualityGate-test.tsx.snap
deleted file mode 100644
index cbf5ab95857..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardQualityGate-test.tsx.snap
+++ /dev/null
@@ -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>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardRatingMeasure-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardRatingMeasure-test.tsx.snap
deleted file mode 100644
index 730efde8faa..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardRatingMeasure-test.tsx.snap
+++ /dev/null
@@ -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>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap
index 7df128ac05e..166b28db57e 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap
@@ -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",
diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.css b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.css
new file mode 100644
index 00000000000..3dc105a138b
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.css
@@ -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;
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx
new file mode 100644
index 00000000000..96012b34c8f
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx
@@ -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>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardLanguages.tsx
index 2814fa79d15..fa413ef8b2f 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardLanguages.tsx
@@ -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>
);
}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguagesContainer.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardLanguagesContainer.tsx
index fb6f9e677d5..dc5af06a0e3 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguagesContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardLanguagesContainer.tsx
@@ -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) => ({
diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasure.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasure.tsx
new file mode 100644
index 00000000000..4581ed80d96
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasure.tsx
@@ -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>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasures.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasures.tsx
new file mode 100644
index 00000000000..b538ad475da
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasures.tsx
@@ -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>
+ </>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardQualityGate.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardQualityGate.tsx
index 50cc00dc38a..adb96da828e 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardQualityGate.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardQualityGate.tsx
@@ -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>
);
}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCard-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCard-test.tsx
index 96ce0408ee0..b10a24efa40 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCard-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCard-test.tsx
@@ -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}
+ />
);
}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLanguages-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardLanguages-test.tsx
index 5e4e78822a5..5e4e78822a5 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLanguages-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardLanguages-test.tsx
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganizationContainer.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardMeasure-test.tsx
index d7141981780..558b3ddba8e 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganizationContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardMeasure-test.tsx
@@ -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} />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardMeasures-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardMeasures-test.tsx
new file mode 100644
index 00000000000..7e0d61fe323
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardMeasures-test.tsx
@@ -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}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardQualityGate-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardQualityGate-test.tsx
index 68232e5212e..68232e5212e 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardQualityGate-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardQualityGate-test.tsx
diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCard-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCard-test.tsx.snap
new file mode 100644
index 00000000000..9b08f38ca38
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCard-test.tsx.snap
@@ -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>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap
new file mode 100644
index 00000000000..a83d0479a91
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap
@@ -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>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardMeasure-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardMeasure-test.tsx.snap
new file mode 100644
index 00000000000..b0c7f29bb95
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardMeasure-test.tsx.snap
@@ -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>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardMeasures-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardMeasures-test.tsx.snap
new file mode 100644
index 00000000000..20d8cf07dd1
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardMeasures-test.tsx.snap
@@ -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>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardQualityGate-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardQualityGate-test.tsx.snap
new file mode 100644
index 00000000000..1cf84c5f4b5
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCardQualityGate-test.tsx.snap
@@ -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>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projects/styles.css b/server/sonar-web/src/main/js/apps/projects/styles.css
index 32f24fed987..f2fd47f42bd 100644
--- a/server/sonar-web/src/main/js/apps/projects/styles.css
+++ b/server/sonar-web/src/main/js/apps/projects/styles.css
@@ -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;
-}
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index 4f1f00eaf45..f2a2941693e 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -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.