]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21766 Project list uses old measures if there are no new ones
authorViktor Vorona <viktor.vorona@sonarsource.com>
Wed, 6 Mar 2024 15:58:21 +0000 (16:58 +0100)
committersonartech <sonartech@sonarsource.com>
Fri, 8 Mar 2024 20:02:34 +0000 (20:02 +0000)
server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx
server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasures.tsx
server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCard-test.tsx
server/sonar-web/src/main/js/apps/projects/utils.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 9713e6484f68de1dfd98810db57e9a428b28d997..1c254aec9c406ba59837ee7a4b82f2b0a91a28fb 100644 (file)
@@ -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)}>
index cfe04620a01da096326b3b45172a6324f2ae8e53..2f9afad7e3286125513ff465632aed907c43eb66 100644 (file)
@@ -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,
         },
index ca0b3a3ac4e0242b6dd7b9fb11c20e3cda7fc148..3167be6de6264040c260cdc58282452494c51926 100644 (file)
@@ -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,
index d3d02f16d9b5c14718728388ad791eb6d126d76d..c31270fc0ee94e96a9efd1ea5cbebe86e043e046 100644 (file)
@@ -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,
index d54c92475830f24a4ba8fbe0a809d924442e5fe1..c829ea2be0f016da8704386ffea4a1ddc297e243 100644 (file)
@@ -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.
+
 
 #------------------------------------------------------------------------------
 #