@@ -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; |
@@ -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> | |||
); | |||
} |
@@ -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> | |||
); | |||
} |
@@ -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> | |||
); | |||
} |
@@ -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> | |||
); | |||
} |
@@ -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> | |||
); | |||
} |
@@ -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' }} |
@@ -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(); | |||
}); |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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> | |||
`; |
@@ -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> | |||
`; |
@@ -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> | |||
`; |
@@ -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> | |||
`; |
@@ -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> | |||
`; |
@@ -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> | |||
`; |
@@ -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", |
@@ -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; | |||
} |
@@ -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> | |||
); | |||
} |
@@ -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> | |||
); | |||
} | |||
@@ -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) => ({ |
@@ -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> | |||
); | |||
} |
@@ -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> | |||
</> | |||
); | |||
} |
@@ -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> | |||
); | |||
} |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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} /> | |||
); | |||
} |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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> | |||
`; |
@@ -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> | |||
`; |
@@ -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> | |||
`; |
@@ -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> | |||
`; |
@@ -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> | |||
`; |
@@ -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; | |||
} |
@@ -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. |