diff options
author | Mathieu Suen <mathieu.suen@sonarsource.com> | 2020-06-18 15:42:33 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2020-09-14 20:07:27 +0000 |
commit | ad4424906d27fc58f51e250532e49009a421fb87 (patch) | |
tree | aa745e9229e4329091ad4cc3098264665b8b6670 | |
parent | 17f30a2dff4ae0c087ccdcbb834b7b873b4a5f17 (diff) | |
download | sonarqube-ad4424906d27fc58f51e250532e49009a421fb87.tar.gz sonarqube-ad4424906d27fc58f51e250532e49009a421fb87.zip |
SONAR-13421 Improve project cards
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. |