From e06cde02ac79b4e571b7bdf7de3efbd4acc260fc Mon Sep 17 00:00:00 2001 From: Viktor Vorona Date: Wed, 6 Mar 2024 16:58:21 +0100 Subject: [PATCH] SONAR-21766 Project list uses old measures if there are no new ones --- .../components/project-card/ProjectCard.tsx | 28 ++++-- .../project-card/ProjectCardMeasures.tsx | 15 ++- .../__tests__/ProjectCard-test.tsx | 95 ++++++++++++++++++- .../src/main/js/apps/projects/utils.ts | 3 + .../resources/org/sonar/l10n/core.properties | 4 + 5 files changed, 132 insertions(+), 13 deletions(-) diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx index 9713e6484f6..1c254aec9c4 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx @@ -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( {translate('visibility', visibility)} + + {awaitingScan && !isNewCode && !isEmpty(analysisDate) && measures.ncloc !== undefined && ( + + + + {translate('projects.awaiting_scan')} + + + + )} {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 ( {qualifier !== ComponentQualifier.Application && - (analysisDate === undefined || analysisDate === '') && + isEmpty(analysisDate) && isLoggedIn(currentUser) && isScannable && ( diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasures.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasures.tsx index cfe04620a01..2f9afad7e32 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasures.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasures.tsx @@ -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, }, diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCard-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCard-test.tsx index ca0b3a3ac4e..3167be6de62 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCard-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCard-test.tsx @@ -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, diff --git a/server/sonar-web/src/main/js/apps/projects/utils.ts b/server/sonar-web/src/main/js/apps/projects/utils.ts index d3d02f16d9b..c31270fc0ee 100644 --- a/server/sonar-web/src/main/js/apps/projects/utils.ts +++ b/server/sonar-web/src/main/js/apps/projects/utils.ts @@ -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, diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index d54c9247583..c829ea2be0f 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -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. + #------------------------------------------------------------------------------ # -- 2.39.5