@@ -20,6 +20,7 @@ | |||
import { getJSON, post, postJSON, RequestData } from 'sonar-ui-common/helpers/request'; | |||
import throwGlobalError from '../app/utils/throwGlobalError'; | |||
import { BranchParameters } from '../types/branch-like'; | |||
import { ComponentQualifier } from '../types/component'; | |||
export interface BaseSearchProjectsParameters { | |||
analyzedBefore?: string; | |||
@@ -207,6 +208,7 @@ export interface Component { | |||
name: string; | |||
isFavorite?: boolean; | |||
analysisDate?: string; | |||
qualifier: ComponentQualifier; | |||
tags: string[]; | |||
visibility: T.Visibility; | |||
leakPeriodDate?: string; |
@@ -144,10 +144,18 @@ th.hide-overflow { | |||
padding-top: var(--gridSize) !important; | |||
} | |||
.padded-right { | |||
padding-right: var(--gridSize) !important; | |||
} | |||
.padded-bottom { | |||
padding-bottom: var(--gridSize) !important; | |||
} | |||
.padded-left { | |||
padding-left: var(--gridSize) !important; | |||
} | |||
.little-padded-top { | |||
padding-top: calc(var(--gridSize) / 2) !important; | |||
} |
@@ -17,10 +17,25 @@ | |||
* 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 { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import PrivacyBadgeContainer from '../../../components/common/PrivacyBadgeContainer'; | |||
import Favorite from '../../../components/controls/Favorite'; | |||
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; | |||
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 ProjectCardLeak from './ProjectCardLeak'; | |||
import ProjectCardOverall from './ProjectCardOverall'; | |||
import { formatDuration } from '../utils'; | |||
import ProjectCardLeakMeasures from './ProjectCardLeakMeasures'; | |||
import ProjectCardOrganizationContainer from './ProjectCardOrganizationContainer'; | |||
import ProjectCardOverallMeasures from './ProjectCardOverallMeasures'; | |||
import ProjectCardQualityGate from './ProjectCardQualityGate'; | |||
interface Props { | |||
currentUser: T.CurrentUser; | |||
@@ -31,11 +46,150 @@ interface Props { | |||
type?: string; | |||
} | |||
export default class ProjectCard extends React.PureComponent<Props> { | |||
render() { | |||
if (this.props.type === 'leak') { | |||
return <ProjectCardLeak {...this.props} />; | |||
} | |||
return <ProjectCardOverall {...this.props} />; | |||
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 { organization, 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"> | |||
{!organization && <ProjectCardOrganizationContainer organization={project.organization} />} | |||
<Link to={getProjectUrl(project.key)}>{project.name}</Link> | |||
</h2> | |||
{project.analysisDate && <ProjectCardQualityGate status={project.measures['alert_status']} />} | |||
<div className="project-card-header-right"> | |||
<PrivacyBadgeContainer | |||
className="spacer-left" | |||
organization={organization || project.organization} | |||
qualifier={project.qualifier} | |||
tooltipProps={{ projectKey: project.key }} | |||
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 dates = getDates(project, type); | |||
return ( | |||
<div | |||
className="boxed-group project-card big-padded display-flex-column display-flex-space-between" | |||
data-key={project.key} | |||
style={{ height }}> | |||
<div> | |||
{renderHeader(props)} | |||
{renderDateRow(project, dates, type)} | |||
</div> | |||
{renderMeasures(props, dates)} | |||
</div> | |||
); | |||
} |
@@ -1,124 +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 difference from 'date-fns/difference_in_milliseconds'; | |||
import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import PrivacyBadgeContainer from '../../../components/common/PrivacyBadgeContainer'; | |||
import Favorite from '../../../components/controls/Favorite'; | |||
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; | |||
import TagsList from '../../../components/tags/TagsList'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
import { isLoggedIn } from '../../../helpers/users'; | |||
import { Project } from '../types'; | |||
import { formatDuration } from '../utils'; | |||
import ProjectCardLeakMeasures from './ProjectCardLeakMeasures'; | |||
import ProjectCardOrganizationContainer from './ProjectCardOrganizationContainer'; | |||
import ProjectCardQualityGate from './ProjectCardQualityGate'; | |||
interface Props { | |||
currentUser: T.CurrentUser; | |||
handleFavorite: (component: string, isFavorite: boolean) => void; | |||
height: number; | |||
organization: T.Organization | undefined; | |||
project: Project; | |||
} | |||
export default class ProjectCardLeak extends React.PureComponent<Props> { | |||
render() { | |||
const { currentUser, handleFavorite, height, organization, project } = this.props; | |||
const { measures } = project; | |||
const hasTags = project.tags.length > 0; | |||
const periodMs = project.leakPeriodDate ? difference(Date.now(), project.leakPeriodDate) : 0; | |||
return ( | |||
<div className="boxed-group project-card" data-key={project.key} style={{ height }}> | |||
<div className="boxed-group-header clearfix"> | |||
<div className="project-card-header"> | |||
{project.isFavorite != null && ( | |||
<Favorite | |||
className="spacer-right" | |||
component={project.key} | |||
favorite={project.isFavorite} | |||
handleFavorite={handleFavorite} | |||
qualifier="TRK" | |||
/> | |||
)} | |||
<h2 className="project-card-name"> | |||
{!organization && ( | |||
<ProjectCardOrganizationContainer organization={project.organization} /> | |||
)} | |||
<Link to={{ pathname: '/dashboard', query: { id: project.key } }}> | |||
{project.name} | |||
</Link> | |||
</h2> | |||
{project.analysisDate && <ProjectCardQualityGate status={measures['alert_status']} />} | |||
<div className="project-card-header-right"> | |||
<PrivacyBadgeContainer | |||
className="spacer-left" | |||
organization={organization || project.organization} | |||
qualifier="TRK" | |||
tooltipProps={{ projectKey: project.key }} | |||
visibility={project.visibility} | |||
/> | |||
{hasTags && <TagsList className="spacer-left note" tags={project.tags} />} | |||
</div> | |||
</div> | |||
{project.analysisDate && project.leakPeriodDate && ( | |||
<div className="project-card-dates note text-right pull-right"> | |||
<span className="project-card-leak-date pull-right"> | |||
{translateWithParameters('projects.new_code_period_x', formatDuration(periodMs))} | |||
</span> | |||
<DateTimeFormatter date={project.analysisDate}> | |||
{formattedDate => ( | |||
<span> | |||
{translateWithParameters('projects.last_analysis_on_x', formattedDate)} | |||
</span> | |||
)} | |||
</DateTimeFormatter> | |||
</div> | |||
)} | |||
</div> | |||
{project.analysisDate && project.leakPeriodDate ? ( | |||
<div className="boxed-group-inner"> | |||
<ProjectCardLeakMeasures measures={measures} /> | |||
</div> | |||
) : ( | |||
<div className="boxed-group-inner"> | |||
<div className="project-card-not-analyzed"> | |||
<span className="note"> | |||
{project.analysisDate | |||
? translate('projects.no_new_code_period') | |||
: translate('projects.not_analyzed')} | |||
</span> | |||
{!project.analysisDate && isLoggedIn(currentUser) && ( | |||
<Link className="button spacer-left" to={getProjectUrl(project.key)}> | |||
{translate('projects.configure_analysis')} | |||
</Link> | |||
)} | |||
</div> | |||
</div> | |||
)} | |||
</div> | |||
); | |||
} | |||
} |
@@ -1,112 +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 { Link } from 'react-router'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import PrivacyBadgeContainer from '../../../components/common/PrivacyBadgeContainer'; | |||
import Favorite from '../../../components/controls/Favorite'; | |||
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; | |||
import TagsList from '../../../components/tags/TagsList'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
import { isLoggedIn } from '../../../helpers/users'; | |||
import { Project } from '../types'; | |||
import ProjectCardOrganizationContainer from './ProjectCardOrganizationContainer'; | |||
import ProjectCardOverallMeasures from './ProjectCardOverallMeasures'; | |||
import ProjectCardQualityGate from './ProjectCardQualityGate'; | |||
interface Props { | |||
currentUser: T.CurrentUser; | |||
handleFavorite: (component: string, isFavorite: boolean) => void; | |||
height: number; | |||
organization: T.Organization | undefined; | |||
project: Project; | |||
} | |||
export default class ProjectCardOverall extends React.PureComponent<Props> { | |||
render() { | |||
const { currentUser, handleFavorite, height, organization, project } = this.props; | |||
const { measures } = project; | |||
const hasTags = project.tags.length > 0; | |||
return ( | |||
<div className="boxed-group project-card" data-key={project.key} style={{ height }}> | |||
<div className="boxed-group-header clearfix"> | |||
<div className="project-card-header"> | |||
{project.isFavorite !== undefined && ( | |||
<Favorite | |||
className="spacer-right" | |||
component={project.key} | |||
favorite={project.isFavorite} | |||
handleFavorite={handleFavorite} | |||
qualifier="TRK" | |||
/> | |||
)} | |||
<h2 className="project-card-name"> | |||
{!organization && ( | |||
<ProjectCardOrganizationContainer organization={project.organization} /> | |||
)} | |||
<Link to={getProjectUrl(project.key)}>{project.name}</Link> | |||
</h2> | |||
{project.analysisDate && <ProjectCardQualityGate status={measures['alert_status']} />} | |||
<div className="project-card-header-right"> | |||
<PrivacyBadgeContainer | |||
className="spacer-left" | |||
organization={organization || project.organization} | |||
qualifier="TRK" | |||
tooltipProps={{ projectKey: project.key }} | |||
visibility={project.visibility} | |||
/> | |||
{hasTags && <TagsList className="spacer-left note" tags={project.tags} />} | |||
</div> | |||
</div> | |||
{project.analysisDate && ( | |||
<div className="project-card-dates note text-right"> | |||
<DateTimeFormatter date={project.analysisDate}> | |||
{formattedDate => ( | |||
<span className="big-spacer-left"> | |||
{translateWithParameters('projects.last_analysis_on_x', formattedDate)} | |||
</span> | |||
)} | |||
</DateTimeFormatter> | |||
</div> | |||
)} | |||
</div> | |||
{project.analysisDate ? ( | |||
<div className="boxed-group-inner"> | |||
{<ProjectCardOverallMeasures measures={measures} />} | |||
</div> | |||
) : ( | |||
<div className="boxed-group-inner"> | |||
<div className="project-card-not-analyzed"> | |||
<span className="note">{translate('projects.not_analyzed')}</span> | |||
{isLoggedIn(currentUser) && ( | |||
<Link className="button spacer-left" to={getProjectUrl(project.key)}> | |||
{translate('projects.configure_analysis')} | |||
</Link> | |||
)} | |||
</div> | |||
</div> | |||
)} | |||
</div> | |||
); | |||
} | |||
} |
@@ -23,21 +23,29 @@ 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({ measures }: Props) { | |||
export default function ProjectCardOverallMeasures({ componentQualifier, measures }: Props) { | |||
if (measures === undefined) { | |||
return null; | |||
} | |||
const { ncloc } = measures; | |||
if (!ncloc) { | |||
return <div className="note">{translate('overview.project.main_branch_empty')}</div>; | |||
return ( | |||
<div className="note big-spacer-top"> | |||
{componentQualifier === ComponentQualifier.Application | |||
? translate('portfolio.app.empty') | |||
: translate('overview.project.main_branch_empty')} | |||
</div> | |||
); | |||
} | |||
return ( |
@@ -22,6 +22,7 @@ import * as React from 'react'; | |||
import { get, save } from 'sonar-ui-common/helpers/storage'; | |||
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | |||
import { isSonarCloud } from '../../../../helpers/system'; | |||
import { ComponentQualifier } from '../../../../types/component'; | |||
import { AllProjects } from '../AllProjects'; | |||
jest.mock('../ProjectsList', () => ({ | |||
@@ -212,7 +213,16 @@ function shallowRender( | |||
); | |||
wrapper.setState({ | |||
loading: false, | |||
projects: [{ key: 'foo', measures: {}, name: 'Foo', tags: [], visibility: 'public' }], | |||
projects: [ | |||
{ | |||
key: 'foo', | |||
measures: {}, | |||
name: 'Foo', | |||
qualifier: ComponentQualifier.Project, | |||
tags: [], | |||
visibility: 'public' | |||
} | |||
], | |||
total: 0 | |||
}); | |||
return wrapper; |
@@ -19,11 +19,15 @@ | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { mockCurrentUser } from '../../../../helpers/testMocks'; | |||
import { mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks'; | |||
import { ComponentQualifier } from '../../../../types/component'; | |||
import { Project } from '../../types'; | |||
import ProjectCard from '../ProjectCard'; | |||
const ORGANIZATION = { key: 'org', name: 'org' }; | |||
jest.mock( | |||
'date-fns/difference_in_milliseconds', | |||
() => () => 1000 * 60 * 60 * 24 * 30 * 8 // ~ 8 months | |||
); | |||
const MEASURES = { | |||
alert_status: 'OK', | |||
@@ -34,36 +38,91 @@ const MEASURES = { | |||
const PROJECT: Project = { | |||
analysisDate: '2017-01-01', | |||
leakPeriodDate: '2016-12-01', | |||
key: 'foo', | |||
measures: MEASURES, | |||
name: 'Foo', | |||
organization: { key: 'org', name: 'org' }, | |||
qualifier: ComponentQualifier.Project, | |||
tags: [], | |||
visibility: 'public' | |||
}; | |||
it('should show <ProjectCardOverall/> by default', () => { | |||
const wrapper = shallowRender(); | |||
expect(wrapper.find('ProjectCardOverall').exists()).toBe(true); | |||
expect(wrapper.find('ProjectCardLeak').exists()).toBe(false); | |||
const USER_LOGGED_OUT = mockCurrentUser(); | |||
const USER_LOGGED_IN = mockLoggedInUser(); | |||
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') | |||
.exists() | |||
).toBe(false); | |||
}); | |||
it('should display tags', () => { | |||
const project = { ...PROJECT, tags: ['foo', 'bar'] }; | |||
expect( | |||
shallowRender(project) | |||
.find('TagsList') | |||
.exists() | |||
).toBe(true); | |||
}); | |||
it('should display private badge', () => { | |||
const project: Project = { ...PROJECT, visibility: 'private' }; | |||
expect( | |||
shallowRender(project) | |||
.find('Connect(PrivacyBadge)') | |||
.exists() | |||
).toBe(true); | |||
}); | |||
it('should display the overall measures and quality gate', () => { | |||
expect(shallowRender(PROJECT)).toMatchSnapshot(); | |||
}); | |||
it('should display not analyzed yet', () => { | |||
expect(shallowRender({ ...PROJECT, analysisDate: undefined })).toMatchSnapshot(); | |||
}); | |||
it('should display configure analysis button for logged in user', () => { | |||
expect(shallowRender({ ...PROJECT, analysisDate: undefined }, USER_LOGGED_IN)).toMatchSnapshot(); | |||
}); | |||
it('should show <ProjectCardLeak/> when asked', () => { | |||
const wrapper = shallowRender('leak'); | |||
expect(wrapper.find('ProjectCardLeak').exists()).toBe(true); | |||
expect(wrapper.find('ProjectCardOverall').exists()).toBe(false); | |||
it('should display applications', () => { | |||
expect( | |||
shallowRender({ ...PROJECT, qualifier: ComponentQualifier.Application }) | |||
).toMatchSnapshot(); | |||
expect( | |||
shallowRender({ | |||
...PROJECT, | |||
qualifier: ComponentQualifier.Application, | |||
measures: { ...MEASURES, projects: '3' } | |||
}) | |||
).toMatchSnapshot('with project count'); | |||
}); | |||
function shallowRender(type?: string) { | |||
function shallowRender(project: Project, user: T.CurrentUser = USER_LOGGED_OUT) { | |||
return shallow( | |||
<ProjectCard | |||
currentUser={mockCurrentUser()} | |||
handleFavorite={jest.fn} | |||
height={200} | |||
organization={ORGANIZATION} | |||
project={PROJECT} | |||
type={type} | |||
currentUser={user} | |||
handleFavorite={jest.fn()} | |||
height={100} | |||
organization={undefined} | |||
project={project} | |||
/> | |||
); | |||
} |
@@ -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 { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks'; | |||
import { Project } from '../../types'; | |||
import ProjectCardLeak from '../ProjectCardLeak'; | |||
jest.mock( | |||
'date-fns/difference_in_milliseconds', | |||
() => () => 1000 * 60 * 60 * 24 * 30 * 8 // ~ 8 months | |||
); | |||
const MEASURES = { | |||
alert_status: 'OK', | |||
reliability_rating: '1.0', | |||
sqale_rating: '1.0', | |||
new_bugs: '12' | |||
}; | |||
const PROJECT: Project = { | |||
analysisDate: '2017-01-01', | |||
leakPeriodDate: '2016-12-01', | |||
key: 'foo', | |||
measures: MEASURES, | |||
name: 'Foo', | |||
organization: { key: 'org', name: 'org' }, | |||
tags: [], | |||
visibility: 'public' | |||
}; | |||
const USER_LOGGED_OUT = mockCurrentUser(); | |||
const USER_LOGGED_IN = mockLoggedInUser(); | |||
it('should display analysis date and leak start date', () => { | |||
const card = shallowRender(PROJECT); | |||
expect(card.find('.project-card-dates').exists()).toBe(true); | |||
expect(card.find('.project-card-dates').find('.project-card-leak-date')).toHaveLength(1); | |||
expect(card.find('.project-card-dates').find('DateTimeFormatter')).toHaveLength(1); | |||
}); | |||
it('should not display analysis date or leak start date', () => { | |||
const project = { ...PROJECT, analysisDate: undefined }; | |||
const card = shallowRender(project); | |||
expect(card.find('.project-card-dates').exists()).toBe(false); | |||
}); | |||
it('should display tags', () => { | |||
const project = { ...PROJECT, tags: ['foo', 'bar'] }; | |||
expect( | |||
shallowRender(project) | |||
.find('TagsList') | |||
.exists() | |||
).toBe(true); | |||
}); | |||
it('should display private badge', () => { | |||
const project: Project = { ...PROJECT, visibility: 'private' }; | |||
expect( | |||
shallowRender(project) | |||
.find('Connect(PrivacyBadge)') | |||
.exists() | |||
).toBe(true); | |||
}); | |||
it('should display the leak measures and quality gate', () => { | |||
expect(shallowRender(PROJECT)).toMatchSnapshot(); | |||
}); | |||
it('should display not analyzed yet', () => { | |||
expect(shallowRender({ ...PROJECT, analysisDate: undefined })).toMatchSnapshot(); | |||
}); | |||
it('should display configure analysis button for logged in user', () => { | |||
expect(shallowRender({ ...PROJECT, analysisDate: undefined }, USER_LOGGED_IN)).toMatchSnapshot(); | |||
}); | |||
function shallowRender(project: Project, user: T.CurrentUser = USER_LOGGED_OUT) { | |||
return shallow( | |||
<ProjectCardLeak | |||
currentUser={user} | |||
handleFavorite={jest.fn()} | |||
height={100} | |||
organization={undefined} | |||
project={project} | |||
/> | |||
); | |||
} |
@@ -1,108 +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 { mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks'; | |||
import { Project } from '../../types'; | |||
import ProjectCardOverall from '../ProjectCardOverall'; | |||
const MEASURES = { | |||
alert_status: 'OK', | |||
reliability_rating: '1.0', | |||
sqale_rating: '1.0', | |||
new_bugs: '12' | |||
}; | |||
const PROJECT: Project = { | |||
analysisDate: '2017-01-01', | |||
key: 'foo', | |||
measures: MEASURES, | |||
name: 'Foo', | |||
organization: { key: 'org', name: 'org' }, | |||
tags: [], | |||
visibility: 'public' | |||
}; | |||
const USER_LOGGED_OUT = mockCurrentUser(); | |||
const USER_LOGGED_IN = mockLoggedInUser(); | |||
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') | |||
.exists() | |||
).toBe(false); | |||
}); | |||
it('should display tags', () => { | |||
const project = { ...PROJECT, tags: ['foo', 'bar'] }; | |||
expect( | |||
shallowRender(project) | |||
.find('TagsList') | |||
.exists() | |||
).toBe(true); | |||
}); | |||
it('should display private badge', () => { | |||
const project: Project = { ...PROJECT, visibility: 'private' }; | |||
expect( | |||
shallowRender(project) | |||
.find('Connect(PrivacyBadge)') | |||
.exists() | |||
).toBe(true); | |||
}); | |||
it('should display the overall measures and quality gate', () => { | |||
expect(shallowRender(PROJECT)).toMatchSnapshot(); | |||
}); | |||
it('should display not analyzed yet', () => { | |||
expect(shallowRender({ ...PROJECT, analysisDate: undefined })).toMatchSnapshot(); | |||
}); | |||
it('should display configure analysis button for logged in user', () => { | |||
expect(shallowRender({ ...PROJECT, analysisDate: undefined }, USER_LOGGED_IN)).toMatchSnapshot(); | |||
}); | |||
function shallowRender(project: Project, user: T.CurrentUser = USER_LOGGED_OUT) { | |||
return shallow( | |||
<ProjectCardOverall | |||
currentUser={user} | |||
handleFavorite={jest.fn()} | |||
height={100} | |||
organization={undefined} | |||
project={project} | |||
/> | |||
); | |||
} |
@@ -19,69 +19,53 @@ | |||
*/ | |||
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', () => { | |||
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' | |||
}; | |||
const wrapper = shallow(<ProjectCardOverallMeasures measures={measures} />); | |||
expect(wrapper).toMatchSnapshot(); | |||
expect(shallowRender()).toMatchSnapshot(); | |||
}); | |||
it('should not render coverage', () => { | |||
const measures = { | |||
alert_status: 'ERROR', | |||
bugs: '17', | |||
code_smells: '132', | |||
duplicated_lines_density: '9.8', | |||
ncloc: '2053', | |||
reliability_rating: '1.0', | |||
security_rating: '1.0', | |||
sqale_rating: '1.0', | |||
vulnerabilities: '0' | |||
}; | |||
const wrapper = shallow(<ProjectCardOverallMeasures measures={measures} />); | |||
expect(wrapper.find('[data-key="coverage"]').exists()).toBe(false); | |||
expect( | |||
shallowRender({ coverage: undefined }) | |||
.find('[data-key="coverage"]') | |||
.exists() | |||
).toBe(false); | |||
}); | |||
it('should render empty', () => { | |||
const measures = { | |||
alert_status: 'ERROR', | |||
bugs: '17', | |||
code_smells: '132', | |||
coverage: '88.3', | |||
duplicated_lines_density: '9.8', | |||
reliability_rating: '1.0', | |||
security_rating: '1.0', | |||
sqale_rating: '1.0', | |||
vulnerabilities: '0' | |||
}; | |||
expect(shallow(<ProjectCardOverallMeasures measures={measures} />)).toMatchSnapshot(); | |||
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', | |||
ncloc: '16549887', | |||
duplicated_lines_density: '9.8', | |||
ncloc: '2053', | |||
reliability_rating: '1.0', | |||
security_rating: '1.0', | |||
sqale_rating: '1.0', | |||
vulnerabilities: '0' | |||
vulnerabilities: '0', | |||
...overriddenMeasures | |||
}; | |||
const wrapper = shallow(<ProjectCardOverallMeasures measures={measures} />); | |||
expect(wrapper.find('[data-key="ncloc"]')).toMatchSnapshot(); | |||
}); | |||
return shallow( | |||
<ProjectCardOverallMeasures | |||
componentQualifier={componentQualifier ?? ComponentQualifier.Project} | |||
measures={measures} | |||
/> | |||
); | |||
} |
@@ -6,6 +6,7 @@ Array [ | |||
"key": "foo", | |||
"measures": Object {}, | |||
"name": "Foo", | |||
"qualifier": "TRK", | |||
"tags": Array [], | |||
"visibility": "public", | |||
}, | |||
@@ -19,6 +20,7 @@ Array [ | |||
"key": "foo", | |||
"measures": Object {}, | |||
"name": "Foo", | |||
"qualifier": "TRK", | |||
"tags": Array [], | |||
"visibility": "public", | |||
}, | |||
@@ -75,6 +77,7 @@ exports[`renders 1`] = ` | |||
"key": "foo", | |||
"measures": Object {}, | |||
"name": "Foo", | |||
"qualifier": "TRK", | |||
"tags": Array [], | |||
"visibility": "public", | |||
}, | |||
@@ -132,6 +135,7 @@ exports[`renders 1`] = ` | |||
"key": "foo", | |||
"measures": Object {}, | |||
"name": "Foo", | |||
"qualifier": "TRK", | |||
"tags": Array [], | |||
"visibility": "public", | |||
}, | |||
@@ -225,6 +229,7 @@ exports[`renders 2`] = ` | |||
"key": "foo", | |||
"measures": Object {}, | |||
"name": "Foo", | |||
"qualifier": "TRK", | |||
"tags": Array [], | |||
"visibility": "public", | |||
}, | |||
@@ -254,6 +259,7 @@ exports[`renders 2`] = ` | |||
"key": "foo", | |||
"measures": Object {}, | |||
"name": "Foo", | |||
"qualifier": "TRK", | |||
"tags": Array [], | |||
"visibility": "public", | |||
}, |
@@ -0,0 +1,455 @@ | |||
// 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" | |||
> | |||
<Connect(ProjectCardOrganization) | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
/> | |||
<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" | |||
> | |||
<Connect(PrivacyBadge) | |||
className="spacer-left" | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
qualifier="APP" | |||
tooltipProps={ | |||
Object { | |||
"projectKey": "foo", | |||
} | |||
} | |||
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" | |||
> | |||
<Connect(ProjectCardOrganization) | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
/> | |||
<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" | |||
> | |||
<Connect(PrivacyBadge) | |||
className="spacer-left" | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
qualifier="APP" | |||
tooltipProps={ | |||
Object { | |||
"projectKey": "foo", | |||
} | |||
} | |||
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" | |||
> | |||
<Connect(ProjectCardOrganization) | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
/> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
Foo | |||
</Link> | |||
</h2> | |||
<div | |||
className="project-card-header-right" | |||
> | |||
<Connect(PrivacyBadge) | |||
className="spacer-left" | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
qualifier="TRK" | |||
tooltipProps={ | |||
Object { | |||
"projectKey": "foo", | |||
} | |||
} | |||
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 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" | |||
> | |||
<Connect(ProjectCardOrganization) | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
/> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
Foo | |||
</Link> | |||
</h2> | |||
<div | |||
className="project-card-header-right" | |||
> | |||
<Connect(PrivacyBadge) | |||
className="spacer-left" | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
qualifier="TRK" | |||
tooltipProps={ | |||
Object { | |||
"projectKey": "foo", | |||
} | |||
} | |||
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" | |||
> | |||
<Connect(ProjectCardOrganization) | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
/> | |||
<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" | |||
> | |||
<Connect(PrivacyBadge) | |||
className="spacer-left" | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
qualifier="TRK" | |||
tooltipProps={ | |||
Object { | |||
"projectKey": "foo", | |||
} | |||
} | |||
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,275 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should display configure analysis button for logged in user 1`] = ` | |||
<div | |||
className="boxed-group project-card" | |||
data-key="foo" | |||
style={ | |||
Object { | |||
"height": 100, | |||
} | |||
} | |||
> | |||
<div | |||
className="boxed-group-header clearfix" | |||
> | |||
<div | |||
className="project-card-header" | |||
> | |||
<h2 | |||
className="project-card-name" | |||
> | |||
<Connect(ProjectCardOrganization) | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
/> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
Foo | |||
</Link> | |||
</h2> | |||
<div | |||
className="project-card-header-right" | |||
> | |||
<Connect(PrivacyBadge) | |||
className="spacer-left" | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
qualifier="TRK" | |||
tooltipProps={ | |||
Object { | |||
"projectKey": "foo", | |||
} | |||
} | |||
visibility="public" | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
<div | |||
className="boxed-group-inner" | |||
> | |||
<div | |||
className="project-card-not-analyzed" | |||
> | |||
<span | |||
className="note" | |||
> | |||
projects.not_analyzed | |||
</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> | |||
`; | |||
exports[`should display not analyzed yet 1`] = ` | |||
<div | |||
className="boxed-group project-card" | |||
data-key="foo" | |||
style={ | |||
Object { | |||
"height": 100, | |||
} | |||
} | |||
> | |||
<div | |||
className="boxed-group-header clearfix" | |||
> | |||
<div | |||
className="project-card-header" | |||
> | |||
<h2 | |||
className="project-card-name" | |||
> | |||
<Connect(ProjectCardOrganization) | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
/> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
Foo | |||
</Link> | |||
</h2> | |||
<div | |||
className="project-card-header-right" | |||
> | |||
<Connect(PrivacyBadge) | |||
className="spacer-left" | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
qualifier="TRK" | |||
tooltipProps={ | |||
Object { | |||
"projectKey": "foo", | |||
} | |||
} | |||
visibility="public" | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
<div | |||
className="boxed-group-inner" | |||
> | |||
<div | |||
className="project-card-not-analyzed" | |||
> | |||
<span | |||
className="note" | |||
> | |||
projects.not_analyzed | |||
</span> | |||
</div> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should display the leak measures and quality gate 1`] = ` | |||
<div | |||
className="boxed-group project-card" | |||
data-key="foo" | |||
style={ | |||
Object { | |||
"height": 100, | |||
} | |||
} | |||
> | |||
<div | |||
className="boxed-group-header clearfix" | |||
> | |||
<div | |||
className="project-card-header" | |||
> | |||
<h2 | |||
className="project-card-name" | |||
> | |||
<Connect(ProjectCardOrganization) | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
/> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
Foo | |||
</Link> | |||
</h2> | |||
<ProjectCardQualityGate | |||
status="OK" | |||
/> | |||
<div | |||
className="project-card-header-right" | |||
> | |||
<Connect(PrivacyBadge) | |||
className="spacer-left" | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
qualifier="TRK" | |||
tooltipProps={ | |||
Object { | |||
"projectKey": "foo", | |||
} | |||
} | |||
visibility="public" | |||
/> | |||
</div> | |||
</div> | |||
<div | |||
className="project-card-dates note text-right pull-right" | |||
> | |||
<span | |||
className="project-card-leak-date pull-right" | |||
> | |||
projects.new_code_period_x.duration.months.8 | |||
</span> | |||
<DateTimeFormatter | |||
date="2017-01-01" | |||
> | |||
<Component /> | |||
</DateTimeFormatter> | |||
</div> | |||
</div> | |||
<div | |||
className="boxed-group-inner" | |||
> | |||
<ProjectCardLeakMeasures | |||
measures={ | |||
Object { | |||
"alert_status": "OK", | |||
"new_bugs": "12", | |||
"reliability_rating": "1.0", | |||
"sqale_rating": "1.0", | |||
} | |||
} | |||
/> | |||
</div> | |||
</div> | |||
`; |
@@ -1,273 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should display configure analysis button for logged in user 1`] = ` | |||
<div | |||
className="boxed-group project-card" | |||
data-key="foo" | |||
style={ | |||
Object { | |||
"height": 100, | |||
} | |||
} | |||
> | |||
<div | |||
className="boxed-group-header clearfix" | |||
> | |||
<div | |||
className="project-card-header" | |||
> | |||
<h2 | |||
className="project-card-name" | |||
> | |||
<Connect(ProjectCardOrganization) | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
/> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
Foo | |||
</Link> | |||
</h2> | |||
<div | |||
className="project-card-header-right" | |||
> | |||
<Connect(PrivacyBadge) | |||
className="spacer-left" | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
qualifier="TRK" | |||
tooltipProps={ | |||
Object { | |||
"projectKey": "foo", | |||
} | |||
} | |||
visibility="public" | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
<div | |||
className="boxed-group-inner" | |||
> | |||
<div | |||
className="project-card-not-analyzed" | |||
> | |||
<span | |||
className="note" | |||
> | |||
projects.not_analyzed | |||
</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> | |||
`; | |||
exports[`should display not analyzed yet 1`] = ` | |||
<div | |||
className="boxed-group project-card" | |||
data-key="foo" | |||
style={ | |||
Object { | |||
"height": 100, | |||
} | |||
} | |||
> | |||
<div | |||
className="boxed-group-header clearfix" | |||
> | |||
<div | |||
className="project-card-header" | |||
> | |||
<h2 | |||
className="project-card-name" | |||
> | |||
<Connect(ProjectCardOrganization) | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
/> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
Foo | |||
</Link> | |||
</h2> | |||
<div | |||
className="project-card-header-right" | |||
> | |||
<Connect(PrivacyBadge) | |||
className="spacer-left" | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
qualifier="TRK" | |||
tooltipProps={ | |||
Object { | |||
"projectKey": "foo", | |||
} | |||
} | |||
visibility="public" | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
<div | |||
className="boxed-group-inner" | |||
> | |||
<div | |||
className="project-card-not-analyzed" | |||
> | |||
<span | |||
className="note" | |||
> | |||
projects.not_analyzed | |||
</span> | |||
</div> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should display the overall measures and quality gate 1`] = ` | |||
<div | |||
className="boxed-group project-card" | |||
data-key="foo" | |||
style={ | |||
Object { | |||
"height": 100, | |||
} | |||
} | |||
> | |||
<div | |||
className="boxed-group-header clearfix" | |||
> | |||
<div | |||
className="project-card-header" | |||
> | |||
<h2 | |||
className="project-card-name" | |||
> | |||
<Connect(ProjectCardOrganization) | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
/> | |||
<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" | |||
> | |||
<Connect(PrivacyBadge) | |||
className="spacer-left" | |||
organization={ | |||
Object { | |||
"key": "org", | |||
"name": "org", | |||
} | |||
} | |||
qualifier="TRK" | |||
tooltipProps={ | |||
Object { | |||
"projectKey": "foo", | |||
} | |||
} | |||
visibility="public" | |||
/> | |||
</div> | |||
</div> | |||
<div | |||
className="project-card-dates note text-right" | |||
> | |||
<DateTimeFormatter | |||
date="2017-01-01" | |||
> | |||
<Component /> | |||
</DateTimeFormatter> | |||
</div> | |||
</div> | |||
<div | |||
className="boxed-group-inner" | |||
> | |||
<ProjectCardOverallMeasures | |||
measures={ | |||
Object { | |||
"alert_status": "OK", | |||
"new_bugs": "12", | |||
"reliability_rating": "1.0", | |||
"sqale_rating": "1.0", | |||
} | |||
} | |||
/> | |||
</div> | |||
</div> | |||
`; |
@@ -180,9 +180,17 @@ exports[`should render correctly with all data 1`] = ` | |||
</div> | |||
`; | |||
exports[`should render empty 1`] = ` | |||
exports[`should render empty: application 1`] = ` | |||
<div | |||
className="note" | |||
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> |
@@ -77,12 +77,6 @@ | |||
white-space: nowrap; | |||
} | |||
.project-card-dates { | |||
width: 100%; | |||
margin-top: 10px; | |||
margin-bottom: -10px; | |||
} | |||
.project-card-leak-date { | |||
padding: 4px 8px; | |||
margin: -5px -4px -5px 24px; |
@@ -17,6 +17,8 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { ComponentQualifier } from '../../types/component'; | |||
export interface Project { | |||
analysisDate?: string; | |||
isFavorite?: boolean; | |||
@@ -25,6 +27,8 @@ export interface Project { | |||
measures: T.Dict<string>; | |||
name: string; | |||
organization?: { key: string; name: string }; | |||
projects?: number; | |||
qualifier: ComponentQualifier; | |||
tags: string[]; | |||
visibility: T.Visibility; | |||
} |
@@ -24,6 +24,7 @@ import { Facet, searchProjects } from '../../api/components'; | |||
import { getMeasuresForProjects } from '../../api/measures'; | |||
import { getOrganizations } from '../../api/organizations'; | |||
import { getPeriodValue, isDiffMetric } from '../../helpers/measures'; | |||
import { MetricKey } from '../../types/metrics'; | |||
import { convertToFilter, Query } from './query'; | |||
interface SortingOption { | |||
@@ -92,44 +93,67 @@ const PAGE_SIZE = 50; | |||
const PAGE_SIZE_VISUALIZATIONS = 99; | |||
const METRICS = [ | |||
'alert_status', | |||
'bugs', | |||
'reliability_rating', | |||
'vulnerabilities', | |||
'security_rating', | |||
'security_hotspots_reviewed', | |||
'security_review_rating', | |||
'code_smells', | |||
'sqale_rating', | |||
'duplicated_lines_density', | |||
'coverage', | |||
'ncloc', | |||
'ncloc_language_distribution' | |||
MetricKey.alert_status, | |||
MetricKey.bugs, | |||
MetricKey.reliability_rating, | |||
MetricKey.vulnerabilities, | |||
MetricKey.security_rating, | |||
MetricKey.security_hotspots_reviewed, | |||
MetricKey.security_review_rating, | |||
MetricKey.code_smells, | |||
MetricKey.sqale_rating, | |||
MetricKey.duplicated_lines_density, | |||
MetricKey.coverage, | |||
MetricKey.ncloc, | |||
MetricKey.ncloc_language_distribution, | |||
MetricKey.projects | |||
]; | |||
const LEAK_METRICS = [ | |||
'alert_status', | |||
'new_bugs', | |||
'new_reliability_rating', | |||
'new_vulnerabilities', | |||
'new_security_rating', | |||
'new_security_hotspots_reviewed', | |||
'new_security_review_rating', | |||
'new_code_smells', | |||
'new_maintainability_rating', | |||
'new_coverage', | |||
'new_duplicated_lines_density', | |||
'new_lines' | |||
MetricKey.alert_status, | |||
MetricKey.new_bugs, | |||
MetricKey.new_reliability_rating, | |||
MetricKey.new_vulnerabilities, | |||
MetricKey.new_security_rating, | |||
MetricKey.new_security_hotspots_reviewed, | |||
MetricKey.new_security_review_rating, | |||
MetricKey.new_code_smells, | |||
MetricKey.new_maintainability_rating, | |||
MetricKey.new_coverage, | |||
MetricKey.new_duplicated_lines_density, | |||
MetricKey.new_lines, | |||
MetricKey.projects | |||
]; | |||
const METRICS_BY_VISUALIZATION: T.Dict<string[]> = { | |||
risk: ['reliability_rating', 'security_rating', 'coverage', 'ncloc', 'sqale_index'], | |||
risk: [ | |||
MetricKey.reliability_rating, | |||
MetricKey.security_rating, | |||
MetricKey.coverage, | |||
MetricKey.ncloc, | |||
MetricKey.sqale_index | |||
], | |||
// x, y, size, color | |||
reliability: ['ncloc', 'reliability_remediation_effort', 'bugs', 'reliability_rating'], | |||
security: ['ncloc', 'security_remediation_effort', 'vulnerabilities', 'security_rating'], | |||
maintainability: ['ncloc', 'sqale_index', 'code_smells', 'sqale_rating'], | |||
coverage: ['complexity', 'coverage', 'uncovered_lines'], | |||
duplications: ['ncloc', 'duplicated_lines_density', 'duplicated_blocks'] | |||
reliability: [ | |||
MetricKey.ncloc, | |||
MetricKey.reliability_remediation_effort, | |||
MetricKey.bugs, | |||
MetricKey.reliability_rating | |||
], | |||
security: [ | |||
MetricKey.ncloc, | |||
MetricKey.security_remediation_effort, | |||
MetricKey.vulnerabilities, | |||
MetricKey.security_rating | |||
], | |||
maintainability: [ | |||
MetricKey.ncloc, | |||
MetricKey.sqale_index, | |||
MetricKey.code_smells, | |||
MetricKey.sqale_rating | |||
], | |||
coverage: [MetricKey.complexity, MetricKey.coverage, MetricKey.uncovered_lines], | |||
duplications: [MetricKey.ncloc, MetricKey.duplicated_lines_density, MetricKey.duplicated_blocks] | |||
}; | |||
export const FACETS = [ |
@@ -20,11 +20,13 @@ | |||
import * as React from 'react'; | |||
import BubbleChart from 'sonar-ui-common/components/charts/BubbleChart'; | |||
import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; | |||
import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import { formatMeasure } from 'sonar-ui-common/helpers/measures'; | |||
import ColorRatingsLegend from '../../../components/charts/ColorRatingsLegend'; | |||
import { RATING_COLORS } from '../../../helpers/constants'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
import { ComponentQualifier } from '../../../types/component'; | |||
import { Project } from '../types'; | |||
const X_METRIC = 'sqale_index'; | |||
@@ -77,7 +79,20 @@ export default class Risk extends React.PureComponent<Props> { | |||
return ( | |||
<div className="text-left"> | |||
<div className="little-spacer-bottom">{fullProjectName}</div> | |||
<div className="little-spacer-bottom display-flex-center display-flex-space-between"> | |||
{fullProjectName} | |||
{project.qualifier === ComponentQualifier.Application && ( | |||
<div className="big-spacer-left nowrap"> | |||
<QualifierIcon | |||
className="little-spacer-right" | |||
fill="currentColor" | |||
qualifier={ComponentQualifier.Application} | |||
/> | |||
{translate('qualifier.APP')} | |||
</div> | |||
)} | |||
</div> | |||
{this.getMetricTooltip({ key: COLOR_METRIC_1, type: COLOR_METRIC_TYPE }, color1)} | |||
{this.getMetricTooltip({ key: COLOR_METRIC_2, type: COLOR_METRIC_TYPE }, color2)} | |||
{this.getMetricTooltip({ key: Y_METRIC, type: Y_METRIC_TYPE }, y)} |
@@ -20,11 +20,13 @@ | |||
import * as React from 'react'; | |||
import BubbleChart from 'sonar-ui-common/components/charts/BubbleChart'; | |||
import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; | |||
import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import { formatMeasure } from 'sonar-ui-common/helpers/measures'; | |||
import ColorRatingsLegend from '../../../components/charts/ColorRatingsLegend'; | |||
import { RATING_COLORS } from '../../../helpers/constants'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
import { ComponentQualifier } from '../../../types/component'; | |||
import { Project } from '../types'; | |||
interface Metric { | |||
@@ -71,7 +73,20 @@ export default class SimpleBubbleChart extends React.PureComponent<Props> { | |||
return ( | |||
<div className="text-left"> | |||
<div className="little-spacer-bottom">{fullProjectName}</div> | |||
<div className="little-spacer-bottom display-flex-center display-flex-space-between"> | |||
{fullProjectName} | |||
{project.qualifier === ComponentQualifier.Application && ( | |||
<div className="big-spacer-left nowrap"> | |||
<QualifierIcon | |||
className="little-spacer-right" | |||
fill="currentColor" | |||
qualifier={ComponentQualifier.Application} | |||
/> | |||
{translate('qualifier.APP')} | |||
</div> | |||
)} | |||
</div> | |||
{this.getMetricTooltip(this.props.xMetric, x)} | |||
{this.getMetricTooltip(this.props.yMetric, y)} | |||
{this.getMetricTooltip(this.props.sizeMetric, size)} |
@@ -19,6 +19,7 @@ | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { ComponentQualifier } from '../../../../types/component'; | |||
import { Project } from '../../types'; | |||
import Risk from '../Risk'; | |||
@@ -27,6 +28,7 @@ it('renders', () => { | |||
key: 'foo', | |||
measures: { complexity: '17.2', coverage: '53.5', ncloc: '1734' }, | |||
name: 'Foo', | |||
qualifier: ComponentQualifier.Project, | |||
tags: [], | |||
visibility: 'public' | |||
}; |
@@ -19,6 +19,7 @@ | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { ComponentQualifier } from '../../../../types/component'; | |||
import { Project } from '../../types'; | |||
import SimpleBubbleChart from '../SimpleBubbleChart'; | |||
@@ -27,16 +28,24 @@ it('renders', () => { | |||
key: 'foo', | |||
measures: { complexity: '17.2', coverage: '53.5', ncloc: '1734', security_rating: '2' }, | |||
name: 'Foo', | |||
qualifier: ComponentQualifier.Project, | |||
tags: [], | |||
visibility: 'public' | |||
}; | |||
const app = { | |||
...project1, | |||
key: 'app', | |||
measures: { complexity: '23.1', coverage: '87.3', ncloc: '32478', security_rating: '1' }, | |||
name: 'App', | |||
qualifier: ComponentQualifier.Application | |||
}; | |||
expect( | |||
shallow( | |||
<SimpleBubbleChart | |||
colorMetric="security_rating" | |||
displayOrganizations={false} | |||
helpText="foobar" | |||
projects={[project1]} | |||
projects={[app, project1]} | |||
sizeMetric={{ key: 'ncloc', type: 'INT' }} | |||
xMetric={{ key: 'complexity', type: 'INT' }} | |||
yMetric={{ key: 'coverage', type: 'PERCENT' }} |
@@ -27,7 +27,7 @@ exports[`renders 1`] = ` | |||
className="text-left" | |||
> | |||
<div | |||
className="little-spacer-bottom" | |||
className="little-spacer-bottom display-flex-center display-flex-space-between" | |||
> | |||
<strong> | |||
Foo |
@@ -12,6 +12,61 @@ exports[`renders 1`] = ` | |||
height={600} | |||
items={ | |||
Array [ | |||
Object { | |||
"color": "#00aa00", | |||
"key": "app", | |||
"link": Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "app", | |||
}, | |||
}, | |||
"size": 32478, | |||
"tooltip": <div | |||
className="text-left" | |||
> | |||
<div | |||
className="little-spacer-bottom display-flex-center display-flex-space-between" | |||
> | |||
<strong> | |||
App | |||
</strong> | |||
<div | |||
className="big-spacer-left nowrap" | |||
> | |||
<QualifierIcon | |||
className="little-spacer-right" | |||
fill="currentColor" | |||
qualifier="APP" | |||
/> | |||
qualifier.APP | |||
</div> | |||
</div> | |||
<div> | |||
metric.complexity.name | |||
: | |||
23 | |||
</div> | |||
<div> | |||
metric.coverage.name | |||
: | |||
87.3% | |||
</div> | |||
<div> | |||
metric.ncloc.name | |||
: | |||
32,478 | |||
</div> | |||
<div> | |||
metric.security_rating.name | |||
: | |||
A | |||
</div> | |||
</div>, | |||
"x": 23.1, | |||
"y": 87.3, | |||
}, | |||
Object { | |||
"color": "#b0d513", | |||
"key": "foo", | |||
@@ -27,7 +82,7 @@ exports[`renders 1`] = ` | |||
className="text-left" | |||
> | |||
<div | |||
className="little-spacer-bottom" | |||
className="little-spacer-bottom display-flex-center display-flex-space-between" | |||
> | |||
<strong> | |||
Foo |
@@ -142,6 +142,7 @@ project=Project | |||
project_x=Project: {0} | |||
projects=Projects | |||
projects_=project(s) | |||
x_projects_={0} project(s) | |||
project_singular=project | |||
project_plural=projects | |||
projects_management=Projects Management | |||
@@ -894,8 +895,10 @@ projects.no_favorite_projects.how_to_add_projects=Here is how to add projects to | |||
projects.no_favorite_projects.favorite_projects_from_orgs=Favorite projects from your orgs | |||
projects.no_favorite_projects.favorite_public_projects=Favorite public projects | |||
projects.explore_projects=Explore Projects | |||
projects.not_analyzed=Project is not analyzed yet. | |||
projects.no_new_code_period=Project has no new code data yet. | |||
projects.not_analyzed.TRK=Project is not analyzed yet. | |||
projects.not_analyzed.APP=None of the Application's projects have been analyzed. | |||
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} | |||
@@ -2913,7 +2916,7 @@ component_measures.bubble_chart.zoom_level=Current zoom level. Scroll on the cha | |||
# ABOUT PAGE | |||
# | |||
#------------------------------------------------------------------------------ | |||
about_page.projects_analyzed=Projects Analyzed | |||
about_page.projects_analyzed=Projects | |||
about_page.read_more=Read More | |||
about_page.read_documentation=Read documentation | |||