@@ -34,6 +34,7 @@ import { | |||
themeBorder, | |||
themeColor, | |||
} from 'design-system'; | |||
import { isEmpty } from 'lodash'; | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import Favorite from '../../../../components/controls/Favorite'; | |||
@@ -66,6 +67,15 @@ function renderFirstLine( | |||
isNewCode: boolean, | |||
) { | |||
const { analysisDate, isFavorite, key, measures, name, qualifier, tags, visibility } = project; | |||
const awaitingScan = | |||
[ | |||
MetricKey.reliability_issues, | |||
MetricKey.maintainability_issues, | |||
MetricKey.security_issues, | |||
].every((key) => measures[key] === undefined) && | |||
!isNewCode && | |||
!isEmpty(analysisDate) && | |||
measures.ncloc !== undefined; | |||
const formatted = formatMeasure(measures[MetricKey.alert_status], MetricType.Level); | |||
const qualityGateLabel = translateWithParameters('overview.quality_gate_x', formatted); | |||
return ( | |||
@@ -112,6 +122,16 @@ function renderFirstLine( | |||
<Badge className="sw-ml-2">{translate('visibility', visibility)}</Badge> | |||
</span> | |||
</Tooltip> | |||
{awaitingScan && !isNewCode && !isEmpty(analysisDate) && measures.ncloc !== undefined && ( | |||
<Tooltip overlay={translate(`projects.awaiting_scan.description.${qualifier}`)}> | |||
<span> | |||
<Badge variant="new" className="sw-ml-2"> | |||
{translate('projects.awaiting_scan')} | |||
</Badge> | |||
</span> | |||
</Tooltip> | |||
)} | |||
</div> | |||
{isDefined(analysisDate) && analysisDate !== '' && ( | |||
@@ -212,11 +232,7 @@ function renderSecondLine( | |||
) { | |||
const { analysisDate, key, leakPeriodDate, measures, qualifier, isScannable } = project; | |||
if ( | |||
isDefined(analysisDate) && | |||
analysisDate !== '' && | |||
(!isNewCode || (isDefined(leakPeriodDate) && leakPeriodDate !== '')) | |||
) { | |||
if (!isEmpty(analysisDate) && (!isNewCode || !isEmpty(leakPeriodDate))) { | |||
return ( | |||
<ProjectCardMeasures | |||
measures={measures} | |||
@@ -235,7 +251,7 @@ function renderSecondLine( | |||
</Note> | |||
{qualifier !== ComponentQualifier.Application && | |||
(analysisDate === undefined || analysisDate === '') && | |||
isEmpty(analysisDate) && | |||
isLoggedIn(currentUser) && | |||
isScannable && ( | |||
<Link className="sw-ml-2 sw-body-sm-highlight" to={getProjectUrl(key)}> |
@@ -121,19 +121,28 @@ function renderRatings(props: ProjectCardMeasuresProps) { | |||
{ | |||
iconLabel: translate(`metric.${MetricKey.security_issues}.short_name`), | |||
noShrink: true, | |||
metricKey: MetricKey.security_issues, | |||
metricKey: | |||
measures[MetricKey.security_issues] !== undefined | |||
? MetricKey.security_issues | |||
: MetricKey.vulnerabilities, | |||
metricRatingKey: MetricKey.security_rating, | |||
metricType: MetricType.ShortInteger, | |||
}, | |||
{ | |||
iconLabel: translate(`metric.${MetricKey.reliability_issues}.short_name`), | |||
metricKey: MetricKey.reliability_issues, | |||
metricKey: | |||
measures[MetricKey.reliability_issues] !== undefined | |||
? MetricKey.reliability_issues | |||
: MetricKey.bugs, | |||
metricRatingKey: MetricKey.reliability_rating, | |||
metricType: MetricType.ShortInteger, | |||
}, | |||
{ | |||
iconLabel: translate(`metric.${MetricKey.maintainability_issues}.short_name`), | |||
metricKey: MetricKey.maintainability_issues, | |||
metricKey: | |||
measures[MetricKey.maintainability_issues] !== undefined | |||
? MetricKey.maintainability_issues | |||
: MetricKey.code_smells, | |||
metricRatingKey: MetricKey.sqale_rating, | |||
metricType: MetricType.ShortInteger, | |||
}, |
@@ -22,15 +22,18 @@ import React from 'react'; | |||
import { mockCurrentUser, mockLoggedInUser } from '../../../../../helpers/testMocks'; | |||
import { renderComponent } from '../../../../../helpers/testReactTestingUtils'; | |||
import { ComponentQualifier, Visibility } from '../../../../../types/component'; | |||
import { MetricKey } from '../../../../../types/metrics'; | |||
import { CurrentUser } from '../../../../../types/users'; | |||
import { Project } from '../../../types'; | |||
import ProjectCard from '../ProjectCard'; | |||
const MEASURES = { | |||
alert_status: 'OK', | |||
reliability_rating: '1.0', | |||
sqale_rating: '1.0', | |||
new_bugs: '12', | |||
[MetricKey.ncloc]: '1000', | |||
[MetricKey.alert_status]: 'OK', | |||
[MetricKey.reliability_rating]: '1.0', | |||
[MetricKey.security_rating]: '1.0', | |||
[MetricKey.sqale_rating]: '1.0', | |||
[MetricKey.new_bugs]: '12', | |||
}; | |||
const PROJECT: Project = { | |||
@@ -81,6 +84,90 @@ it('should display applications', () => { | |||
expect(screen.getByLabelText('qualifier.APP')).toBeInTheDocument(); | |||
}); | |||
it('should not display awaiting analysis badge and do not display old measures', () => { | |||
renderProjectCard({ | |||
...PROJECT, | |||
measures: { | |||
...MEASURES, | |||
[MetricKey.security_issues]: JSON.stringify({ LOW: 0, MEDIUM: 0, HIGH: 1, total: 1 }), | |||
[MetricKey.reliability_issues]: JSON.stringify({ LOW: 0, MEDIUM: 2, HIGH: 0, total: 2 }), | |||
[MetricKey.maintainability_issues]: JSON.stringify({ LOW: 3, MEDIUM: 0, HIGH: 0, total: 3 }), | |||
[MetricKey.code_smells]: '4', | |||
[MetricKey.bugs]: '5', | |||
[MetricKey.vulnerabilities]: '6', | |||
}, | |||
}); | |||
expect(screen.queryByRole('status', { name: 'projects.awaiting_scan' })).not.toBeInTheDocument(); | |||
expect(screen.getByText('1')).toBeInTheDocument(); | |||
expect(screen.getByText('2')).toBeInTheDocument(); | |||
expect(screen.getByText('3')).toBeInTheDocument(); | |||
expect(screen.queryByText('4')).not.toBeInTheDocument(); | |||
expect(screen.queryByText('5')).not.toBeInTheDocument(); | |||
expect(screen.queryByText('6')).not.toBeInTheDocument(); | |||
}); | |||
it('should display awaiting analysis badge and show the old measures', async () => { | |||
renderProjectCard({ | |||
...PROJECT, | |||
measures: { | |||
...MEASURES, | |||
[MetricKey.code_smells]: '4', | |||
[MetricKey.bugs]: '5', | |||
[MetricKey.vulnerabilities]: '6', | |||
}, | |||
}); | |||
expect(screen.getByRole('status', { name: 'projects.awaiting_scan' })).toBeInTheDocument(); | |||
await expect( | |||
screen.getByRole('status', { name: 'projects.awaiting_scan' }), | |||
).toHaveATooltipWithContent('projects.awaiting_scan.description.TRK'); | |||
expect(screen.getByText('4')).toBeInTheDocument(); | |||
expect(screen.getByText('5')).toBeInTheDocument(); | |||
expect(screen.getByText('6')).toBeInTheDocument(); | |||
}); | |||
it('should display awaiting analysis badge and show the old measures for Application', async () => { | |||
renderProjectCard({ | |||
...PROJECT, | |||
qualifier: ComponentQualifier.Application, | |||
measures: { | |||
...MEASURES, | |||
[MetricKey.code_smells]: '4', | |||
[MetricKey.bugs]: '5', | |||
[MetricKey.vulnerabilities]: '6', | |||
}, | |||
}); | |||
expect(screen.getByRole('status', { name: 'projects.awaiting_scan' })).toBeInTheDocument(); | |||
await expect( | |||
screen.getByRole('status', { name: 'projects.awaiting_scan' }), | |||
).toHaveATooltipWithContent('projects.awaiting_scan.description.APP'); | |||
expect(screen.getByText('4')).toBeInTheDocument(); | |||
expect(screen.getByText('5')).toBeInTheDocument(); | |||
expect(screen.getByText('6')).toBeInTheDocument(); | |||
}); | |||
it('should not display awaiting analysis badge if project is not analyzed', () => { | |||
renderProjectCard({ | |||
...PROJECT, | |||
analysisDate: undefined, | |||
}); | |||
expect(screen.queryByRole('status', { name: 'projects.awaiting_scan' })).not.toBeInTheDocument(); | |||
}); | |||
it('should not display awaiting analysis badge if project does not have lines of code', () => { | |||
renderProjectCard({ | |||
...PROJECT, | |||
measures: { | |||
...(({ [MetricKey.ncloc]: _, ...rest }) => rest)(MEASURES), | |||
}, | |||
}); | |||
expect(screen.queryByRole('status', { name: 'projects.awaiting_scan' })).not.toBeInTheDocument(); | |||
}); | |||
it('should not display awaiting analysis badge if it is a new code filter', () => { | |||
renderProjectCard(PROJECT, undefined, 'leak'); | |||
expect(screen.queryByRole('status', { name: 'projects.awaiting_scan' })).not.toBeInTheDocument(); | |||
}); | |||
it('should display 3 aplication', () => { | |||
renderProjectCard({ | |||
...PROJECT, |
@@ -91,10 +91,13 @@ const PAGE_SIZE = 50; | |||
export const METRICS = [ | |||
MetricKey.alert_status, | |||
MetricKey.reliability_issues, | |||
MetricKey.bugs, | |||
MetricKey.reliability_rating, | |||
MetricKey.security_issues, | |||
MetricKey.vulnerabilities, | |||
MetricKey.security_rating, | |||
MetricKey.maintainability_issues, | |||
MetricKey.code_smells, | |||
MetricKey.sqale_rating, | |||
MetricKey.security_hotspots_reviewed, | |||
MetricKey.security_review_rating, |
@@ -1329,6 +1329,10 @@ projects.sort.size=by size (smallest first) | |||
projects.sort.-size=by size (biggest first) | |||
projects.show_more=Show more projects | |||
projects.security_hotspots_reviewed=Hotspots Reviewed | |||
projects.awaiting_scan=Change in Calculation | |||
projects.awaiting_scan.description.TRK=The way Security, Reliability, and Maintainability counts are calculated has changed. The values currently displayed may change after the next analysis. | |||
projects.awaiting_scan.description.APP=The way Security, Reliability, and Maintainability counts are calculated has changed. The values currently displayed may change after all projects in this application have been analyzed. | |||
#------------------------------------------------------------------------------ | |||
# |