]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21767 Project overview show old count when analysis data is missing
authorIsmail Cherri <ismail.cherri@sonarsource.com>
Thu, 7 Mar 2024 20:08:35 +0000 (14:08 -0600)
committersonartech <sonartech@sonarsource.com>
Fri, 8 Mar 2024 20:02:34 +0000 (20:02 +0000)
server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx
server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx
server/sonar-web/src/main/js/apps/overview/branches/TabsPanel.tsx
server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx
server/sonar-web/src/main/js/apps/overview/branches/test-utils.ts
server/sonar-web/src/main/js/components/shared/AnalysisMissingInfoMessage.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/helpers/issues.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index c56c808d48b57a88526886752cd91a8079ef9d17..5faad7c2615251c052123d448c650c2e80f1958c 100644 (file)
  */
 import {
   BasicSeparator,
-  FlagMessage,
   LargeCenteredLayout,
   LightGreyCard,
   PageContentFontWrapper,
 } from 'design-system';
 import * as React from 'react';
-import { useIntl } from 'react-intl';
 import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import { useLocation, useRouter } from '../../../components/hoc/withRouter';
+import AnalysisMissingInfoMessage from '../../../components/shared/AnalysisMissingInfoMessage';
 import { parseDate } from '../../../helpers/dates';
 import { isDiffMetric } from '../../../helpers/measures';
 import { CodeScope } from '../../../helpers/urls';
 import { ApplicationPeriod } from '../../../types/application';
 import { Branch } from '../../../types/branch-like';
-import { ComponentQualifier, isApplication } from '../../../types/component';
+import { ComponentQualifier } from '../../../types/component';
 import { MetricKey } from '../../../types/metrics';
 import { Analysis, GraphType, MeasureHistory } from '../../../types/project-activity';
 import { QualityGateStatus } from '../../../types/quality-gates';
@@ -94,18 +93,17 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp
   const { query } = useLocation();
   const router = useRouter();
 
-  const intl = useIntl();
   const tab = query.codeScope === CodeScope.Overall ? CodeScope.Overall : CodeScope.New;
   const leakPeriod = component.qualifier === ComponentQualifier.Application ? appLeak : period;
   const isNewCodeTab = tab === CodeScope.New;
   const hasNewCodeMeasures = measures.some((m) => isDiffMetric(m.metric.key));
 
   // Check if any potentially missing uncomputed measure is not present
-  const isMissingMeasures = (
-    isNewCodeTab
-      ? [MetricKey.new_accepted_issues]
-      : [MetricKey.security_issues, MetricKey.maintainability_issues, MetricKey.reliability_issues]
-  ).some((key) => !measures.find((measure) => measure.metric.key === key));
+  const isMissingMeasures = [
+    MetricKey.security_issues,
+    MetricKey.maintainability_issues,
+    MetricKey.reliability_issues,
+  ].some((key) => !measures.find((measure) => measure.metric.key === key));
 
   const selectTab = (tab: CodeScope) => {
     router.replace({ query: { ...query, codeScope: tab } });
@@ -121,14 +119,9 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp
     /* eslint-disable-next-line react-hooks/exhaustive-deps */
   }, [loadingStatus, hasNewCodeMeasures]);
 
-  const appReanalysisWarning =
-    isMissingMeasures && isApplication(component.qualifier) ? (
-      <FlagMessage variant="warning" className="sw-my-4">
-        {intl.formatMessage({
-          id: 'overview.missing_project_data.APP',
-        })}
-      </FlagMessage>
-    ) : null;
+  const analysisMissingInfo = isMissingMeasures && (
+    <AnalysisMissingInfoMessage qualifier={component.qualifier} className="sw-mt-6" />
+  );
 
   return (
     <>
@@ -184,15 +177,12 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp
                         {isNewCodeTab && (
                           <>
                             {hasNewCodeMeasures ? (
-                              <>
-                                {appReanalysisWarning}
-                                <NewCodeMeasuresPanel
-                                  qgStatuses={qgStatuses}
-                                  branch={branch}
-                                  component={component}
-                                  measures={measures}
-                                />
-                              </>
+                              <NewCodeMeasuresPanel
+                                qgStatuses={qgStatuses}
+                                branch={branch}
+                                component={component}
+                                measures={measures}
+                              />
                             ) : (
                               <MeasuresPanelNoNewCode
                                 branch={branch}
@@ -205,7 +195,7 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp
 
                         {!isNewCodeTab && (
                           <>
-                            {appReanalysisWarning}
+                            {analysisMissingInfo}
                             <OverallCodeMeasuresPanel
                               branch={branch}
                               qgStatuses={qgStatuses}
index 501f22da8fdd99db44458a1b34fd35f857ffcbfa..52fcb66b01608a2cce42c48c583667e51a7c8b8e 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { LinkHighlight, LinkStandalone } from '@sonarsource/echoes-react';
 import styled from '@emotion/styled';
 import classNames from 'classnames';
-import {
-  BasicSeparator,
-  LightGreyCard,
-  NakedLink,
-  TextBold,
-  TextSubdued,
-  themeColor,
-} from 'design-system';
+import { BasicSeparator, LightGreyCard, TextBold, TextSubdued } from 'design-system';
 import * as React from 'react';
 import { useIntl } from 'react-intl';
 import Tooltip from '../../../components/controls/Tooltip';
@@ -45,6 +39,11 @@ import { OverviewDisabledLinkTooltip } from '../components/OverviewDisabledLinkT
 import { softwareQualityToMeasure } from '../utils';
 import SoftwareImpactMeasureBreakdownCard from './SoftwareImpactMeasureBreakdownCard';
 import SoftwareImpactMeasureRating from './SoftwareImpactMeasureRating';
+import { isDefined } from '../../../helpers/types';
+import {
+  getIssueTypeBySoftwareQuality,
+  SOFTWARE_QUALITIES_METRIC_KEYS_MAP,
+} from '../../../helpers/issues';
 
 export interface SoftwareImpactBreakdownCardProps {
   component: Component;
@@ -63,15 +62,20 @@ export function SoftwareImpactMeasureCard(props: Readonly<SoftwareImpactBreakdow
   const metricKey = softwareQualityToMeasure(softwareQuality);
   const measureRaw = measures.find((m) => m.metric.key === metricKey);
   const measure = JSON.parse(measureRaw?.value ?? 'null') as SoftwareImpactMeasureData;
-
-  const renderDisabled = !measure || component.needIssueSync;
+  const alternativeMeasure = measures.find(
+    (m) => m.metric.key === SOFTWARE_QUALITIES_METRIC_KEYS_MAP[softwareQuality].deprecatedMetric,
+  );
 
   // Find rating measure
   const ratingMeasure = measures.find((m) => m.metric.key === ratingMetricKey);
 
+  const count = measure?.total ?? alternativeMeasure?.value;
+
   const totalLinkHref = getComponentIssuesUrl(component.key, {
     ...DEFAULT_ISSUES_QUERY,
-    impactSoftwareQualities: softwareQuality,
+    ...(isDefined(measure)
+      ? { impactSoftwareQualities: softwareQuality }
+      : { types: getIssueTypeBySoftwareQuality(softwareQuality) }),
     branch: branch?.name,
   });
 
@@ -99,30 +103,30 @@ export function SoftwareImpactMeasureCard(props: Readonly<SoftwareImpactBreakdow
         <div className="sw-flex sw-mt-2">
           <div
             className={classNames('sw-flex sw-gap-1 sw-items-center', {
-              'sw-opacity-60': renderDisabled,
+              'sw-opacity-60': component.needIssueSync,
             })}
           >
-            {measure ? (
+            {count ? (
               <Tooltip overlay={countTooltipOverlay}>
-                <NakedLink
+                <LinkStandalone
                   data-testid={`overview__software-impact-${softwareQuality}`}
                   aria-label={intl.formatMessage(
                     {
                       id: `overview.measures.software_impact.see_list_of_x_open_issues`,
                     },
                     {
-                      count: measure.total,
+                      count,
                       softwareQuality: intl.formatMessage({
                         id: `software_quality.${softwareQuality}`,
                       }),
                     },
                   )}
-                  className="sw-text-lg"
+                  className="sw-text-lg sw-font-semibold"
+                  highlight={LinkHighlight.CurrentColor}
                   to={totalLinkHref}
-                  disabled={component.needIssueSync}
                 >
-                  {formatMeasure(measure.total, MetricType.ShortInteger)}
-                </NakedLink>
+                  {formatMeasure(count, MetricType.ShortInteger)}
+                </LinkStandalone>
               </Tooltip>
             ) : (
               <StyledDash className="sw-font-bold" name="-" />
@@ -139,36 +143,26 @@ export function SoftwareImpactMeasureCard(props: Readonly<SoftwareImpactBreakdow
             />
           </div>
         </div>
-        <div className="sw-flex sw-gap-2">
-          {[
-            SoftwareImpactSeverity.High,
-            SoftwareImpactSeverity.Medium,
-            SoftwareImpactSeverity.Low,
-          ].map((severity) => (
-            <SoftwareImpactMeasureBreakdownCard
-              branch={branch}
-              key={severity}
-              component={component}
-              softwareQuality={softwareQuality}
-              value={measure?.[severity]?.toString()}
-              severity={severity}
-              active={highlightedSeverity === severity}
-            />
-          ))}
-        </div>
+        {measure && (
+          <div className="sw-flex sw-gap-2">
+            {[
+              SoftwareImpactSeverity.High,
+              SoftwareImpactSeverity.Medium,
+              SoftwareImpactSeverity.Low,
+            ].map((severity) => (
+              <SoftwareImpactMeasureBreakdownCard
+                branch={branch}
+                key={severity}
+                component={component}
+                softwareQuality={softwareQuality}
+                value={measure?.[severity]?.toString()}
+                severity={severity}
+                active={highlightedSeverity === severity}
+              />
+            ))}
+          </div>
+        )}
       </div>
-      {!measure && (
-        <>
-          <BasicSeparator className="sw--mx-4 sw-mb-0 sw-mt-3" />
-          <StyledInfoSection className="sw--ml-4 sw--mr-4 sw--mb-4 sw-text-xs sw-p-4 sw-flex sw-gap-1 sw-flex-wrap">
-            <span>
-              {intl.formatMessage({
-                id: `overview.run_analysis_to_compute.${component.qualifier}`,
-              })}
-            </span>
-          </StyledInfoSection>
-        </>
-      )}
     </LightGreyCard>
   );
 }
@@ -177,8 +171,4 @@ const StyledDash = styled(TextBold)`
   font-size: 36px;
 `;
 
-const StyledInfoSection = styled.div`
-  background-color: ${themeColor('overviewSoftwareImpactSeverityNeutral')};
-`;
-
 export default SoftwareImpactMeasureCard;
index 489860ba4591df8856afe391f8caf965e7ce21dd..de0ca9c98305ef2a35a8f2d17d98172d8f94bdfa 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { Spinner } from '@sonarsource/echoes-react';
 import { isBefore, sub } from 'date-fns';
-import {
-  BasicSeparator,
-  ButtonLink,
-  FlagMessage,
-  LightLabel,
-  PageTitle,
-  Spinner,
-  Tabs,
-} from 'design-system';
+import { BasicSeparator, ButtonLink, FlagMessage, LightLabel, Tabs } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
 import DocumentationLink from '../../../components/common/DocumentationLink';
@@ -131,15 +124,14 @@ export function TabsPanel(props: React.PropsWithChildren<MeasuresPanelProps>) {
 
   return (
     <div data-testid="overview__measures-panel">
-      <div className="sw-flex sw-justify-between sw-items-center sw-mb-4">
-        <PageTitle as="h2" text={translate('overview.measures')} />
+      <div className="sw-flex sw-justify-end sw-items-center sw-mb-4">
         <LastAnalysisLabel analysisDate={branch?.analysisDate} />
       </div>
       <BasicSeparator className="sw--mx-6 sw-mb-3" />
 
       {loading ? (
         <div>
-          <Spinner loading={loading} />
+          <Spinner isLoading={loading} />
         </div>
       ) : (
         <>
index ba8015aa4c143ffe95bd283f18d96991501af74b..d4ffff4011fb98fce684140ae2e9e9bc9ab01083 100644 (file)
@@ -37,7 +37,7 @@ import { mockAnalysis, mockAnalysisEvent } from '../../../../helpers/mocks/proje
 import { mockQualityGateProjectStatus } from '../../../../helpers/mocks/quality-gates';
 import { mockLoggedInUser, mockMeasure, mockPaging } from '../../../../helpers/testMocks';
 import { renderComponent } from '../../../../helpers/testReactTestingUtils';
-import { byRole, byText } from '../../../../helpers/testSelector';
+import { byLabelText, byRole, byText } from '../../../../helpers/testSelector';
 import { SoftwareImpactSeverity, SoftwareQuality } from '../../../../types/clean-code-taxonomy';
 import { ComponentQualifier } from '../../../../types/component';
 import { MetricKey } from '../../../../types/metrics';
@@ -323,9 +323,11 @@ describe('project overview', () => {
     );
   });
 
-  it('should render missing software impact measure cards', async () => {
-    // Make as if reliability_issues was not computed
+  it('should render old measures if software impact are missing', async () => {
+    // Make as if new analysis after upgrade is missing
     measuresHandler.deleteComponentMeasure('foo', MetricKey.maintainability_issues);
+    measuresHandler.deleteComponentMeasure('foo', MetricKey.security_issues);
+    measuresHandler.deleteComponentMeasure('foo', MetricKey.reliability_issues);
 
     const { user, ui } = getPageObjects();
     renderBranchOverview();
@@ -334,43 +336,91 @@ describe('project overview', () => {
 
     expect(await ui.softwareImpactMeasureCard(SoftwareQuality.Security).find()).toBeInTheDocument();
 
-    ui.expectSoftwareImpactMeasureCard(
+    ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Security);
+    ui.expectSoftwareImpactMeasureCardToHaveOldMeasures(
       SoftwareQuality.Security,
       'B',
-      {
-        total: 1,
-        [SoftwareImpactSeverity.High]: 0,
-        [SoftwareImpactSeverity.Medium]: 1,
-        [SoftwareImpactSeverity.Low]: 0,
-      },
-      [false, true, false],
+      2,
+      'VULNERABILITY',
     );
-    ui.expectSoftwareImpactMeasureCard(
-      SoftwareQuality.Reliability,
-      'A',
-      {
-        total: 3,
-        [SoftwareImpactSeverity.High]: 0,
-        [SoftwareImpactSeverity.Medium]: 2,
-        [SoftwareImpactSeverity.Low]: 1,
-      },
-      [false, true, false],
+
+    ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Reliability);
+    ui.expectSoftwareImpactMeasureCardToHaveOldMeasures(SoftwareQuality.Reliability, 'A', 0, 'BUG');
+
+    ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Maintainability);
+    ui.expectSoftwareImpactMeasureCardToHaveOldMeasures(
+      SoftwareQuality.Maintainability,
+      'E',
+      8,
+      'CODE_SMELL',
     );
+  });
+
+  it('should render missing software impact measure cards if both software qualities and old measures are missing', async () => {
+    // Make as if no measures at all
+    measuresHandler.deleteComponentMeasure('foo', MetricKey.maintainability_issues);
+    measuresHandler.deleteComponentMeasure('foo', MetricKey.code_smells);
+
+    measuresHandler.deleteComponentMeasure('foo', MetricKey.security_issues);
+    measuresHandler.deleteComponentMeasure('foo', MetricKey.vulnerabilities);
+
+    measuresHandler.deleteComponentMeasure('foo', MetricKey.reliability_issues);
+    measuresHandler.deleteComponentMeasure('foo', MetricKey.bugs);
+
+    const { user, ui } = getPageObjects();
+    renderBranchOverview();
+
+    await user.click(await ui.overallCodeButton.find());
+
+    expect(await ui.softwareImpactMeasureCard(SoftwareQuality.Security).find()).toBeInTheDocument();
+
+    expect(byText('-', { exact: true }).getAll()).toHaveLength(3);
+
+    ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Security);
+    expect(
+      byLabelText(
+        `overview.project.software_impact.has_rating.software_quality.${SoftwareQuality.Security}.B`,
+      ).get(),
+    ).toBeInTheDocument();
+
+    ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Reliability);
+    expect(
+      byLabelText(
+        `overview.project.software_impact.has_rating.software_quality.${SoftwareQuality.Reliability}.A`,
+      ).get(),
+    ).toBeInTheDocument();
 
-    // Maintainability is not computed
     ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Maintainability);
     expect(
-      byText('overview.run_analysis_to_compute.TRK').get(
-        ui.softwareImpactMeasureCard(SoftwareQuality.Maintainability).get(),
-      ),
+      byLabelText(
+        `overview.project.software_impact.has_rating.software_quality.${SoftwareQuality.Maintainability}.E`,
+      ).get(),
     ).toBeInTheDocument();
-    ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Maintainability, undefined, undefined, [
-      false,
-      false,
-      false,
-    ]);
   });
 
+  it.each([
+    ['security_issues', MetricKey.security_issues],
+    ['reliability_issues', MetricKey.reliability_issues],
+    ['maintainability_issues', MetricKey.maintainability_issues],
+  ])(
+    'should display info about missing analysis if a project is not computed for %s',
+    async (missingMetricKey) => {
+      measuresHandler.deleteComponentMeasure('foo', missingMetricKey as MetricKey);
+      const { user, ui } = getPageObjects();
+      renderBranchOverview();
+
+      await user.click(await ui.overallCodeButton.find());
+
+      expect(
+        await ui.softwareImpactMeasureCard(SoftwareQuality.Security).find(),
+      ).toBeInTheDocument();
+
+      await user.click(await ui.overallCodeButton.find());
+
+      expect(await screen.findByText('overview.missing_project_data.TRK')).toBeInTheDocument();
+    },
+  );
+
   it('should disable software impact measure card links during reindexing', async () => {
     const { user, ui } = getPageObjects();
     renderBranchOverview({
@@ -528,17 +578,6 @@ describe('application overview', () => {
     expect(await screen.findByText('portfolio.app.empty')).toBeInTheDocument();
   });
 
-  it.each([['new_accepted_issues', MetricKey.new_accepted_issues]])(
-    'should ask to reanalyze all projects if a project is not computed for %s',
-    async (missingMetricKey) => {
-      measuresHandler.deleteComponentMeasure('foo', missingMetricKey as MetricKey);
-
-      renderBranchOverview({ component });
-
-      expect(await screen.findByText('overview.missing_project_data.APP')).toBeInTheDocument();
-    },
-  );
-
   it.each([
     ['security_issues', MetricKey.security_issues],
     ['reliability_issues', MetricKey.reliability_issues],
index d748b941b4808d747364830e715e7008110ae34f..21925f6e06673f023bed696c4cec22bd6ae491aa 100644 (file)
@@ -100,6 +100,26 @@ export const getPageObjects = () => {
         );
       }
     },
+    expectSoftwareImpactMeasureCardToHaveOldMeasures: (
+      softwareQuality: SoftwareQuality,
+      rating: string,
+      total: number,
+      oldMetric: string,
+      branch = 'master',
+    ) => {
+      const branchQuery = branch ? `&branch=${branch}` : '';
+      expect(
+        byText(rating, { exact: true }).get(ui.softwareImpactMeasureCard(softwareQuality).get()),
+      ).toBeInTheDocument();
+      expect(
+        byRole('link', {
+          name: `overview.measures.software_impact.see_list_of_x_open_issues.${total}.software_quality.${softwareQuality}`,
+        }).get(),
+      ).toHaveAttribute(
+        'href',
+        `/project/issues?issueStatuses=OPEN%2CCONFIRMED&types=${oldMetric}${branchQuery}&id=foo`,
+      );
+    },
     expectSoftwareImpactMeasureBreakdownCard: (
       softwareQuality: SoftwareQuality,
       severity: SoftwareImpactSeverity,
diff --git a/server/sonar-web/src/main/js/components/shared/AnalysisMissingInfoMessage.tsx b/server/sonar-web/src/main/js/components/shared/AnalysisMissingInfoMessage.tsx
new file mode 100644 (file)
index 0000000..685f987
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 { FlagMessage } from 'design-system';
+import * as React from 'react';
+import { FormattedMessage, useIntl } from 'react-intl';
+import DocumentationLink from '../common/DocumentationLink';
+
+interface AnalysisMissingInfoMessageProps {
+  qualifier: string;
+  className?: string;
+}
+
+export default function AnalysisMissingInfoMessage({
+  qualifier,
+  className,
+}: Readonly<AnalysisMissingInfoMessageProps>) {
+  const intl = useIntl();
+
+  return (
+    <FlagMessage variant="info" className={className}>
+      <FormattedMessage
+        id={`overview.missing_project_data.${qualifier}`}
+        tagName="div"
+        values={{
+          learn_more: (
+            <DocumentationLink
+              className="sw-ml-2 sw-whitespace-nowrap"
+              to="/user-guide/clean-code/code-analysis/"
+            >
+              {intl.formatMessage({ id: 'learn_more' })}
+            </DocumentationLink>
+          ),
+        }}
+      />
+    </FlagMessage>
+  );
+}
index bbccf072160ea6f11d6a308d38e4bea7ad579b17..c62a9b2922281115072d71646aee303ac54477b6 100644 (file)
@@ -24,6 +24,7 @@ import { MetricKey } from '../types/metrics';
 import { Dict, Flow, FlowLocation, FlowType, Issue, TextRange } from '../types/types';
 import { UserBase } from '../types/users';
 import { ISSUE_TYPES } from './constants';
+import { SoftwareQuality } from '../types/clean-code-taxonomy';
 
 interface Rule {}
 
@@ -163,6 +164,37 @@ export function parseIssueFromResponse(
   } as Issue;
 }
 
+export function getIssueTypeBySoftwareQuality(quality: SoftwareQuality): IssueType {
+  const map = {
+    [SoftwareQuality.Maintainability]: IssueType.CodeSmell,
+    [SoftwareQuality.Security]: IssueType.Vulnerability,
+    [SoftwareQuality.Reliability]: IssueType.Bug,
+  };
+
+  return map[quality];
+}
+
+export const SOFTWARE_QUALITIES_METRIC_KEYS_MAP = {
+  [SoftwareQuality.Security]: {
+    metric: MetricKey.security_issues,
+    deprecatedMetric: MetricKey.vulnerabilities,
+    rating: MetricKey.security_rating,
+    newRating: MetricKey.new_security_rating,
+  },
+  [SoftwareQuality.Reliability]: {
+    metric: MetricKey.reliability_issues,
+    deprecatedMetric: MetricKey.bugs,
+    rating: MetricKey.reliability_rating,
+    newRating: MetricKey.new_reliability_rating,
+  },
+  [SoftwareQuality.Maintainability]: {
+    metric: MetricKey.maintainability_issues,
+    deprecatedMetric: MetricKey.code_smells,
+    rating: MetricKey.sqale_rating,
+    newRating: MetricKey.new_maintainability_rating,
+  },
+};
+
 export const ISSUETYPE_METRIC_KEYS_MAP = {
   [IssueType.CodeSmell]: {
     metric: MetricKey.code_smells,
index c829ea2be0f016da8704386ffea4a1ddc297e243..2092486d43a4878a127c5f5eaa10fc2f1bbdaf98 100644 (file)
@@ -3929,7 +3929,6 @@ overview.accepted_issues=Accepted issues
 overview.accepted_issues.description=Issues that are valid, but were not fixed and represent accepted technical debt.
 overview.accepted_issues.total=Total accepted issues
 overview.high_impact_accepted_issues=High impact accepted issues
-overview.measures=Measures
 overview.measures.empty_explanation=Measures on New Code will appear after the second analysis of this branch.
 overview.measures.empty_link={learn_more_link} about the Clean as You Code approach.
 overview.measures.same_reference.explanation=This branch is configured to use itself as reference branch. It will never have New Code.
@@ -3969,7 +3968,8 @@ overview.project.next_steps.links.set_up_ci=set up analysis in your favorite CI
 overview.project.software_impact.has_rating=Software Quality {softwareQuality} has rating {rating}
 overview.run_analysis_to_compute.TRK=Run new analysis to compute the missing data.
 overview.run_analysis_to_compute.APP=Analyse all projects to compute the missing data.
-overview.missing_project_data.APP=Some projects are missing data. All projects in the application need to be analysed to compute the missing data.
+overview.missing_project_data.APP=The way Security, Reliability, and Maintainability are calculated has changed. These values may change after all projects in this application have been analyzed again. {learn_more}
+overview.missing_project_data.TRK=The way Security, Reliability, and Maintainability are calculated has changed. These values may change after the next analysis. {learn_more}
 
 overview.coverage_on=Coverage on
 overview.coverage_on_X_lines=Coverage on {count} Lines to cover