]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21799 Measures page reflects software qualities
authorIsmail Cherri <ismail.cherri@sonarsource.com>
Mon, 18 Mar 2024 09:07:15 +0000 (10:07 +0100)
committersonartech <sonartech@sonarsource.com>
Mon, 25 Mar 2024 20:02:42 +0000 (20:02 +0000)
18 files changed:
server/sonar-web/src/main/js/api/mocks/data/measures.ts
server/sonar-web/src/main/js/apps/component-measures/__tests__/ComponentMeasures-it.tsx
server/sonar-web/src/main/js/apps/component-measures/components/ComponentMeasuresApp.tsx
server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx
server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx
server/sonar-web/src/main/js/apps/component-measures/config/domains.ts
server/sonar-web/src/main/js/apps/component-measures/drilldown/MeasureCell.tsx
server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigation.tsx
server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigationItem.tsx
server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx
server/sonar-web/src/main/js/apps/component-measures/sidebar/SubnavigationMeasureValue.tsx
server/sonar-web/src/main/js/apps/component-measures/utils.ts
server/sonar-web/src/main/js/helpers/constants.ts
server/sonar-web/src/main/js/helpers/measures.ts
server/sonar-web/src/main/js/helpers/mocks/metrics.ts
server/sonar-web/src/main/js/types/measures.ts
server/sonar-web/src/main/js/types/metrics.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 6c571bb07037493c4093728bf748daa3fd952cbd..91f1480c84a10d23d48fc217be54769d3af82674 100644 (file)
@@ -28,6 +28,7 @@ import { ComponentTree } from './components';
 import { IssueData } from './issues';
 import { listAllComponent, listAllComponentTrees } from './utils';
 
+const MAX_RATING = 5;
 export type MeasureRecords = Record<string, Record<string, Measure>>;
 
 export function mockFullMeasureData(tree: ComponentTree, issueList: IssueData[]) {
@@ -68,6 +69,21 @@ function mockComponentMeasure(tree: ComponentTree, issueList: IssueData[], metri
         }),
       });
 
+    case MetricKey.new_security_issues:
+      return mockMeasure({
+        metric: metricKey,
+        period: {
+          index: 1,
+          value: JSON.stringify({
+            total: 3,
+            [SoftwareImpactSeverity.High]: 2,
+            [SoftwareImpactSeverity.Medium]: 0,
+            [SoftwareImpactSeverity.Low]: 1,
+          }),
+        },
+        value: undefined,
+      });
+
     case MetricKey.reliability_issues:
       return mockMeasure({
         metric: metricKey,
@@ -79,6 +95,21 @@ function mockComponentMeasure(tree: ComponentTree, issueList: IssueData[], metri
         }),
       });
 
+    case MetricKey.new_reliability_issues:
+      return mockMeasure({
+        metric: metricKey,
+        period: {
+          index: 1,
+          value: JSON.stringify({
+            total: 2,
+            [SoftwareImpactSeverity.High]: 0,
+            [SoftwareImpactSeverity.Medium]: 1,
+            [SoftwareImpactSeverity.Low]: 1,
+          }),
+        },
+        value: undefined,
+      });
+
     case MetricKey.maintainability_issues:
       return mockMeasure({
         metric: metricKey,
@@ -89,6 +120,21 @@ function mockComponentMeasure(tree: ComponentTree, issueList: IssueData[], metri
           [SoftwareImpactSeverity.Low]: 1,
         }),
       });
+
+    case MetricKey.new_maintainability_issues:
+      return mockMeasure({
+        metric: metricKey,
+        period: {
+          index: 1,
+          value: JSON.stringify({
+            total: 5,
+            [SoftwareImpactSeverity.High]: 2,
+            [SoftwareImpactSeverity.Medium]: 2,
+            [SoftwareImpactSeverity.Low]: 1,
+          }),
+        },
+        value: undefined,
+      });
   }
 
   const issues = issueList
@@ -234,13 +280,16 @@ function mockComponentMeasure(tree: ComponentTree, issueList: IssueData[], metri
 export function getMetricTypeFromKey(metricKey: string) {
   if (/(coverage|duplication)$/.test(metricKey)) {
     return MetricType.Percent;
-  } else if (/_rating$/.test(metricKey)) {
+  } else if (metricKey.includes('_rating')) {
     return MetricType.Rating;
   } else if (
     [
       MetricKey.reliability_issues,
+      MetricKey.new_reliability_issues,
       MetricKey.security_issues,
+      MetricKey.new_security_issues,
       MetricKey.maintainability_issues,
+      MetricKey.new_maintainability_issues,
     ].includes(metricKey as MetricKey)
   ) {
     return MetricType.Data;
@@ -276,7 +325,7 @@ function isIssueRelatedRating(metricKey: MetricKey) {
  * ratio to the LOC. But using the number will suffice as an approximation in our tests.
  */
 function computeRating(issues: RawIssue[], type: IssueType) {
-  const value = Math.max(Math.min(issues.filter((i) => i.type === type).length, 5), 1);
+  const value = Math.max(Math.min(issues.filter((i) => i.type === type).length, MAX_RATING), 1);
   return {
     value: `${value}.0`,
     bestValue: value === 1,
index 80db0b61b39f587c0c179627705bdf74d2e51c72..2f115c6d4dd8da1854490f54651c9cc8ad9c2cd5 100644 (file)
@@ -88,11 +88,11 @@ describe('rendering', () => {
     // Check one of the domains.
     await user.click(ui.maintainabilityDomainBtn.get());
     [
-      'New Code Smells 8',
+      'component_measures.metric.new_maintainability_issues.name 5',
       'Added Technical Debt work_duration.x_minutes.1',
       'Technical Debt Ratio on New Code 1.0%',
       'Maintainability Rating on New Code metric.has_rating_X.E',
-      'Code Smells 8',
+      'component_measures.metric.maintainability_issues.name 2',
       'Technical Debt work_duration.x_minutes.1',
       'Technical Debt Ratio 1.0%',
       'Maintainability Rating metric.has_rating_X.E',
@@ -102,6 +102,32 @@ describe('rendering', () => {
     });
   });
 
+  it('should correctly revert to old measures when analysis is missing', async () => {
+    measuresHandler.deleteComponentMeasure('foo', MetricKey.maintainability_issues);
+    measuresHandler.deleteComponentMeasure('foo', MetricKey.new_maintainability_issues);
+
+    const { ui, user } = getPageObject();
+    renderMeasuresApp();
+    await ui.appLoaded();
+
+    // Check one of the domains.
+    await user.click(ui.maintainabilityDomainBtn.get());
+    [
+      'component_measures.metric.new_code_smells.name 8',
+      'Added Technical Debt work_duration.x_minutes.1',
+      'Technical Debt Ratio on New Code 1.0%',
+      'Maintainability Rating on New Code metric.has_rating_X.E',
+      'component_measures.metric.code_smells.name 8',
+      'Technical Debt work_duration.x_minutes.1',
+      'Technical Debt Ratio 1.0%',
+      'Maintainability Rating metric.has_rating_X.E',
+      'Effort to Reach Maintainability Rating A work_duration.x_minutes.1',
+    ].forEach((measure) => {
+      expect(ui.measureBtn(measure).get()).toBeInTheDocument();
+    });
+    expect(screen.getByText('overview.missing_project_data.TRK')).toBeInTheDocument();
+  });
+
   it('should correctly render a list view', async () => {
     const { ui } = getPageObject();
     renderMeasuresApp('component_measures?id=foo&metric=code_smells&view=list');
@@ -172,7 +198,17 @@ describe('rendering', () => {
     renderMeasuresApp('component_measures?id=foo&metric=open_issues');
     await ui.appLoaded();
 
-    expect(screen.getAllByText('Issues').length).toBeGreaterThan(1);
+    expect(screen.getAllByText('Issues').length).toEqual(1);
+    [
+      'component_measures.metric.new_violations.name 1',
+      'component_measures.metric.violations.name 1',
+      'component_measures.metric.confirmed_issues.name 1',
+      'component_measures.metric.accepted_issues.name 1',
+      'component_measures.metric.new_accepted_issues.name 1',
+      'component_measures.metric.false_positive_issues.name 1',
+    ].forEach((measure) => {
+      expect(ui.measureBtn(measure).get()).toBeInTheDocument();
+    });
   });
 
   it('should render correctly if there are no measures', async () => {
@@ -267,14 +303,16 @@ describe('rendering', () => {
 
   it('should correctly render a link to the activity page', async () => {
     const { ui, user } = getPageObject();
-    renderMeasuresApp('component_measures?id=foo&metric=new_code_smells');
+    renderMeasuresApp('component_measures?id=foo&metric=new_maintainability_issues');
     await ui.appLoaded();
 
     expect(ui.goToActivityLink.query()).not.toBeInTheDocument();
-    await user.click(ui.measureBtn('Code Smells 8').get());
+    await user.click(
+      ui.measureBtn('component_measures.metric.maintainability_issues.name 2').get(),
+    );
     expect(ui.goToActivityLink.get()).toHaveAttribute(
       'href',
-      '/project/activity?id=foo&graph=custom&custom_metrics=code_smells',
+      '/project/activity?id=foo&graph=custom&custom_metrics=maintainability_issues',
     );
   });
 
@@ -305,9 +343,11 @@ describe('navigation', () => {
     // Drilldown to the file level.
     await user.click(ui.maintainabilityDomainBtn.get());
 
-    await user.click(ui.measureBtn('Code Smells 8').get());
+    await user.click(
+      ui.measureBtn('component_measures.metric.maintainability_issues.name 2').get(),
+    );
     expect(
-      within(ui.measuresRow('folderA').get()).getByRole('cell', { name: '3' }),
+      within(ui.measuresRow('folderA').get()).getByRole('cell', { name: '2' }),
     ).toBeInTheDocument();
     expect(
       within(ui.measuresRow('test1.js').get()).getByRole('cell', { name: '2' }),
@@ -315,7 +355,7 @@ describe('navigation', () => {
 
     await user.click(ui.fileLink('folderA').get());
     expect(
-      within(ui.measuresRow('out.tsx').get()).getByRole('cell', { name: '1' }),
+      within(ui.measuresRow('out.tsx').get()).getByRole('cell', { name: '2' }),
     ).toBeInTheDocument();
     expect(
       within(ui.measuresRow('in.tsx').get()).getByRole('cell', { name: '2' }),
@@ -336,11 +376,13 @@ describe('navigation', () => {
     await ui.appLoaded();
 
     await user.click(ui.maintainabilityDomainBtn.get());
-    await user.click(ui.measureBtn('Code Smells 8').get());
+    await user.click(
+      ui.measureBtn('component_measures.metric.maintainability_issues.name 2').get(),
+    );
     await waitFor(() => ui.changeViewToList());
 
     expect(
-      within(await ui.measuresRow('out.tsx').find()).getByRole('cell', { name: '1' }),
+      within(await ui.measuresRow('out.tsx').find()).getByRole('cell', { name: '2' }),
     ).toBeInTheDocument();
     expect(
       within(ui.measuresRow('test1.js').get()).getByRole('cell', { name: '2' }),
@@ -378,13 +420,15 @@ describe('navigation', () => {
 
     // Drilldown to the file level.
     await user.click(ui.maintainabilityDomainBtn.get());
-    await user.click(ui.measureBtn('Code Smells 8').get());
+    await user.click(
+      ui.measureBtn('component_measures.metric.maintainability_issues.name 2').get(),
+    );
 
     await ui.arrowDown(); // Select the 1st element ("folderA")
     await ui.arrowRight(); // Open "folderA"
 
     expect(
-      within(ui.measuresRow('out.tsx').get()).getByRole('cell', { name: '1' }),
+      within(ui.measuresRow('out.tsx').get()).getByRole('cell', { name: '2' }),
     ).toBeInTheDocument();
     expect(
       within(ui.measuresRow('in.tsx').get()).getByRole('cell', { name: '2' }),
@@ -394,7 +438,7 @@ describe('navigation', () => {
     await ui.arrowLeft(); // Close "folderA"
 
     expect(
-      within(ui.measuresRow('folderA').get()).getByRole('cell', { name: '3' }),
+      within(ui.measuresRow('folderA').get()).getByRole('cell', { name: '2' }),
     ).toBeInTheDocument();
 
     await ui.arrowRight(); // Open "folderA"
@@ -416,18 +460,44 @@ describe('redirects', () => {
   });
 
   it('should redirect old metric route', async () => {
+    measuresHandler.deleteComponentMeasure('foo', MetricKey.maintainability_issues);
+    measuresHandler.deleteComponentMeasure('foo', MetricKey.new_maintainability_issues);
+
     const { ui } = getPageObject();
     renderMeasuresApp('component_measures/metric/bugs?id=foo');
     await ui.appLoaded();
-    expect(ui.measureBtn('Bugs 0').get()).toHaveAttribute('aria-current', 'true');
+    expect(ui.measureBtn('component_measures.metric.bugs.name 0').get()).toHaveAttribute(
+      'aria-current',
+      'true',
+    );
+  });
+
+  it('should redirect old metric route for software qualities', async () => {
+    const { ui } = getPageObject();
+    renderMeasuresApp('component_measures/metric/security_issues?id=foo');
+    await ui.appLoaded();
+    expect(ui.measureBtn('component_measures.metric.security_issues.name 1').get()).toHaveAttribute(
+      'aria-current',
+      'true',
+    );
   });
 
   it('should redirect old domain route', async () => {
+    measuresHandler.deleteComponentMeasure('foo', MetricKey.maintainability_issues);
+    measuresHandler.deleteComponentMeasure('foo', MetricKey.new_maintainability_issues);
+
     const { ui } = getPageObject();
     renderMeasuresApp('component_measures/domain/bugs?id=foo');
     await ui.appLoaded();
     expect(ui.reliabilityDomainBtn.get()).toHaveAttribute('aria-expanded', 'true');
   });
+
+  it('should redirect old domain route for software qualities', async () => {
+    const { ui } = getPageObject();
+    renderMeasuresApp('component_measures/domain/reliability_issues?id=foo');
+    await ui.appLoaded();
+    expect(ui.reliabilityDomainBtn.get()).toHaveAttribute('aria-expanded', 'true');
+  });
 });
 
 it('should allow to load more components', async () => {
index e0ea9d3461b251d1eafaaeb7c6211cf9ad6d8d60..72b5e0bbdd74d26e6d0f2fb9ae808b59d2189bad 100644 (file)
@@ -21,6 +21,7 @@ import { withTheme } from '@emotion/react';
 import styled from '@emotion/styled';
 import { Spinner } from '@sonarsource/echoes-react';
 import {
+  FlagMessage,
   LargeCenteredLayout,
   Note,
   PageContentFontWrapper,
@@ -33,12 +34,15 @@ import { Helmet } from 'react-helmet-async';
 import { getMeasuresWithPeriod } from '../../../api/measures';
 import { getAllMetrics } from '../../../api/metrics';
 import { ComponentContext } from '../../../app/components/componentContext/ComponentContext';
+import HelpTooltip from '../../../components/controls/HelpTooltip';
 import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
 import { enhanceMeasure } from '../../../components/measure/utils';
 import '../../../components/search-navigator.css';
+import AnalysisMissingInfoMessage from '../../../components/shared/AnalysisMissingInfoMessage';
 import { getBranchLikeQuery, isPullRequest, isSameBranchLike } from '../../../helpers/branch-like';
 import { translate } from '../../../helpers/l10n';
+import { areLeakAndOverallCCTMeasuresComputed } from '../../../helpers/measures';
 import { WithBranchLikesProps, useBranchesQuery } from '../../../queries/branch';
 import { ComponentQualifier, isPortfolioLike } from '../../../types/component';
 import { MeasurePageView } from '../../../types/measures';
@@ -134,7 +138,10 @@ class ComponentMeasuresApp extends React.PureComponent<Props, State> {
   fetchMeasures(metrics: State['metrics']) {
     const { branchLike } = this.props;
     const query = parseQuery(this.props.location.query);
-    const componentKey = query.selected || this.props.component.key;
+    const componentKey =
+      query.selected !== undefined && query.selected !== ''
+        ? query.selected
+        : this.props.component.key;
 
     const filteredKeys = getMeasuresPageMetricKeys(metrics, branchLike);
 
@@ -285,15 +292,25 @@ class ComponentMeasuresApp extends React.PureComponent<Props, State> {
           {measures.length > 0 ? (
             <div className="sw-grid sw-grid-cols-12 sw-w-full">
               <Sidebar
-                canBrowseAllChildProjects={!!canBrowseAllChildProjects}
                 measures={measures}
-                qualifier={qualifier}
                 selectedMetric={metric ? metric.key : query.metric}
                 showFullMeasures={showFullMeasures}
                 updateQuery={this.updateQuery}
               />
 
               <div className="sw-col-span-9 sw-ml-12">
+                {!canBrowseAllChildProjects && isPortfolioLike(qualifier) && (
+                  <FlagMessage className="sw-mb-4 it__portfolio_warning" variant="warning">
+                    {translate('component_measures.not_all_measures_are_shown')}
+                    <HelpTooltip
+                      className="sw-ml-2"
+                      overlay={translate('component_measures.not_all_measures_are_shown.help')}
+                    />
+                  </FlagMessage>
+                )}
+                {!areLeakAndOverallCCTMeasuresComputed(measures) && (
+                  <AnalysisMissingInfoMessage className="sw-mb-4" qualifier={qualifier} />
+                )}
                 {this.renderContent(displayOverview, query, metric)}
               </div>
             </div>
index ae408fbdccdb66eea1b7362bf237fe7544fccba6..6c31247d8176465180109e2e78bae3558606295b 100644 (file)
@@ -29,7 +29,7 @@ import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branch-li
 import { getComponentMeasureUniqueKey } from '../../../helpers/component';
 import { KeyboardKeys } from '../../../helpers/keycodes';
 import { translate } from '../../../helpers/l10n';
-import { isDiffMetric } from '../../../helpers/measures';
+import { getCCTMeasureValue, isDiffMetric } from '../../../helpers/measures';
 import { RequestData } from '../../../helpers/request';
 import { isDefined } from '../../../helpers/types';
 import { getProjectUrl } from '../../../helpers/urls';
@@ -94,8 +94,14 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
   }
 
   componentDidUpdate(prevProps: Props) {
-    const prevComponentKey = prevProps.selected || prevProps.rootComponent.key;
-    const componentKey = this.props.selected || this.props.rootComponent.key;
+    const prevComponentKey =
+      prevProps.selected !== undefined && prevProps.selected !== ''
+        ? prevProps.selected
+        : prevProps.rootComponent.key;
+    const componentKey =
+      this.props.selected !== undefined && this.props.selected !== ''
+        ? this.props.selected
+        : this.props.rootComponent.key;
     if (
       prevComponentKey !== componentKey ||
       !isSameBranchLike(prevProps.branchLike, this.props.branchLike) ||
@@ -116,7 +122,7 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
     const { metricKeys, opts, strategy } = this.getComponentRequestParams(view, requestedMetric, {
       ...(asc !== undefined && { asc }),
     });
-    const componentKey = selected || rootComponent.key;
+    const componentKey = selected !== undefined && selected !== '' ? selected : rootComponent.key;
     const baseComponentMetrics = [requestedMetric.key];
     if (requestedMetric.key === MetricKey.ncloc) {
       baseComponentMetrics.push(MetricKey.ncloc_language_distribution);
@@ -347,8 +353,10 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
       return null;
     }
 
-    const measureValue =
+    const rawMeasureValue =
       measure && (isDiffMetric(measure.metric) ? measure.period?.value : measure.value);
+    const measureValue = getCCTMeasureValue(metric.key, rawMeasureValue);
+
     const isFileComponent = isFile(baseComponent.qualifier);
     const selectedIdx = this.getSelectedIndex();
 
index 0d7849925274d78a271c4ce9377fc226fd222904..a6790e2af7ad561e278d41865ba32890e1a88652 100644 (file)
@@ -17,8 +17,9 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { LinkStandalone } from '@sonarsource/echoes-react';
 import classNames from 'classnames';
-import { Link, MetricsLabel, MetricsRatingBadge } from 'design-system';
+import { MetricsLabel, MetricsRatingBadge } from 'design-system';
 import * as React from 'react';
 import LanguageDistribution from '../../../components/charts/LanguageDistribution';
 import Tooltip from '../../../components/controls/Tooltip';
@@ -30,7 +31,7 @@ import { BranchLike } from '../../../types/branch-like';
 import { ComponentQualifier } from '../../../types/component';
 import { MetricKey, MetricType } from '../../../types/metrics';
 import { ComponentMeasure, Metric, Period, Measure as TypeMeasure } from '../../../types/types';
-import { hasFullMeasures } from '../utils';
+import { getMetricSubnavigationName, hasFullMeasures } from '../utils';
 import LeakPeriodLegend from './LeakPeriodLegend';
 
 interface Props {
@@ -42,7 +43,7 @@ interface Props {
   secondaryMeasure?: TypeMeasure;
 }
 
-export default function MeasureHeader(props: Props) {
+export default function MeasureHeader(props: Readonly<Props>) {
   const { branchLike, component, leakPeriod, measureValue, metric, secondaryMeasure } = props;
   const isDiff = isDiffMetric(metric.key);
   const hasHistory =
@@ -53,11 +54,13 @@ export default function MeasureHeader(props: Props) {
       ComponentQualifier.Project,
     ].includes(component.qualifier as ComponentQualifier) && hasFullMeasures(branchLike);
   const displayLeak = hasFullMeasures(branchLike);
+  const title = getMetricSubnavigationName(metric, getLocalizedMetricName, isDiff);
+
   return (
     <div className="sw-mb-4">
       <div className="sw-flex sw-items-center sw-justify-between sw-gap-4">
         <div className="it__measure-details-metric sw-flex sw-items-center sw-gap-1">
-          <strong className="sw-body-md-highlight">{getLocalizedMetricName(metric)}</strong>
+          <strong className="sw-body-md-highlight">{title}</strong>
 
           <div className="sw-flex sw-items-center sw-ml-2">
             <Measure
@@ -84,12 +87,12 @@ export default function MeasureHeader(props: Props) {
           {!isDiff && hasHistory && (
             <Tooltip overlay={translate('component_measures.show_metric_history')}>
               <span className="sw-ml-4">
-                <Link
+                <LinkStandalone
                   className="it__show-history-link sw-font-semibold"
                   to={getMeasureHistoryUrl(component.key, metric.key, branchLike)}
                 >
                   {translate('component_measures.see_metric_history')}
-                </Link>
+                </LinkStandalone>
               </span>
             </Tooltip>
           )}
index f653b0cde0ee6d8fe6fdb39d482f9d34bb79e150..b9c6fcd8f3e11788c9ad0c0df8ca8e306b5236f3 100644 (file)
@@ -23,16 +23,21 @@ interface Domains {
   [domain: string]: { categories?: string[]; order: string[] };
 }
 
+const NEW_CODE_CATEGORY = 'new_code_category';
+const OVERALL_CATEGORY = 'overall_category';
+
 export const domains: Domains = {
   Reliability: {
-    categories: ['new_code_category', 'overall_category'],
+    categories: [NEW_CODE_CATEGORY, OVERALL_CATEGORY],
     order: [
-      'new_code_category',
+      NEW_CODE_CATEGORY,
+      MetricKey.new_reliability_issues,
       MetricKey.new_bugs,
       MetricKey.new_reliability_rating,
       MetricKey.new_reliability_remediation_effort,
 
-      'overall_category',
+      OVERALL_CATEGORY,
+      MetricKey.reliability_issues,
       MetricKey.bugs,
       MetricKey.reliability_rating,
       MetricKey.reliability_remediation_effort,
@@ -40,14 +45,16 @@ export const domains: Domains = {
   },
 
   Security: {
-    categories: ['new_code_category', 'overall_category'],
+    categories: [NEW_CODE_CATEGORY, OVERALL_CATEGORY],
     order: [
-      'new_code_category',
+      NEW_CODE_CATEGORY,
+      MetricKey.new_security_issues,
       MetricKey.new_vulnerabilities,
       MetricKey.new_security_rating,
       MetricKey.new_security_remediation_effort,
 
-      'overall_category',
+      OVERALL_CATEGORY,
+      MetricKey.security_issues,
       MetricKey.vulnerabilities,
       MetricKey.security_rating,
       MetricKey.security_remediation_effort,
@@ -55,14 +62,14 @@ export const domains: Domains = {
   },
 
   SecurityReview: {
-    categories: ['new_code_category', 'overall_category'],
+    categories: [NEW_CODE_CATEGORY, OVERALL_CATEGORY],
     order: [
-      'new_code_category',
+      NEW_CODE_CATEGORY,
       MetricKey.new_security_hotspots,
       MetricKey.new_security_review_rating,
       MetricKey.new_security_hotspots_reviewed,
 
-      'overall_category',
+      OVERALL_CATEGORY,
       MetricKey.security_hotspots,
       MetricKey.security_review_rating,
       MetricKey.security_hotspots_reviewed,
@@ -70,15 +77,17 @@ export const domains: Domains = {
   },
 
   Maintainability: {
-    categories: ['new_code_category', 'overall_category'],
+    categories: [NEW_CODE_CATEGORY, OVERALL_CATEGORY],
     order: [
-      'new_code_category',
+      NEW_CODE_CATEGORY,
+      MetricKey.new_maintainability_issues,
       MetricKey.new_code_smells,
       MetricKey.new_technical_debt,
       MetricKey.new_sqale_debt_ratio,
       MetricKey.new_maintainability_rating,
 
-      'overall_category',
+      OVERALL_CATEGORY,
+      MetricKey.maintainability_issues,
       MetricKey.code_smells,
       MetricKey.sqale_index,
       MetricKey.sqale_debt_ratio,
@@ -88,9 +97,9 @@ export const domains: Domains = {
   },
 
   Coverage: {
-    categories: ['new_code_category', 'overall_category', 'tests_category'],
+    categories: [NEW_CODE_CATEGORY, OVERALL_CATEGORY, 'tests_category'],
     order: [
-      'new_code_category',
+      NEW_CODE_CATEGORY,
       MetricKey.new_coverage,
       MetricKey.new_lines_to_cover,
       MetricKey.new_uncovered_lines,
@@ -99,7 +108,7 @@ export const domains: Domains = {
       MetricKey.new_uncovered_conditions,
       MetricKey.new_branch_coverage,
 
-      'overall_category',
+      OVERALL_CATEGORY,
       MetricKey.coverage,
       MetricKey.lines_to_cover,
       MetricKey.uncovered_lines,
@@ -119,14 +128,14 @@ export const domains: Domains = {
   },
 
   Duplications: {
-    categories: ['new_code_category', 'overall_category'],
+    categories: [NEW_CODE_CATEGORY, OVERALL_CATEGORY],
     order: [
-      'new_code_category',
+      NEW_CODE_CATEGORY,
       MetricKey.new_duplicated_lines_density,
       MetricKey.new_duplicated_lines,
       MetricKey.new_duplicated_blocks,
 
-      'overall_category',
+      OVERALL_CATEGORY,
       MetricKey.duplicated_lines_density,
       MetricKey.duplicated_lines,
       MetricKey.duplicated_blocks,
@@ -157,23 +166,16 @@ export const domains: Domains = {
   },
 
   Issues: {
+    categories: [NEW_CODE_CATEGORY, OVERALL_CATEGORY],
     order: [
+      NEW_CODE_CATEGORY,
       MetricKey.new_violations,
-      MetricKey.new_blocker_violations,
-      MetricKey.new_critical_violations,
-      MetricKey.new_major_violations,
-      MetricKey.new_minor_violations,
-      MetricKey.new_info_violations,
+      MetricKey.new_accepted_issues,
 
+      OVERALL_CATEGORY,
       MetricKey.violations,
-      MetricKey.blocker_violations,
-      MetricKey.critical_violations,
-      MetricKey.major_violations,
-      MetricKey.minor_violations,
-      MetricKey.info_violations,
-      MetricKey.open_issues,
-      MetricKey.reopened_issues,
       MetricKey.confirmed_issues,
+      MetricKey.accepted_issues,
       MetricKey.false_positive_issues,
     ],
   },
index dc2a073033a1dae327b30c61bb9dcd362a33ee9b..56a1ac297449243d5d69c4a59f6af9d59c8a8369 100644 (file)
@@ -21,7 +21,7 @@ import { MetricsLabel, MetricsRatingBadge, NumericalCell } from 'design-system';
 import * as React from 'react';
 import Measure from '../../../components/measure/Measure';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
+import { formatMeasure, getCCTMeasureValue, isDiffMetric } from '../../../helpers/measures';
 import { MetricType } from '../../../types/metrics';
 import { ComponentMeasureEnhanced, MeasureEnhanced, Metric } from '../../../types/types';
 
@@ -35,7 +35,8 @@ export default function MeasureCell({ component, measure, metric }: Props) {
   const getValue = (item: { leak?: string; value?: string }) =>
     isDiffMetric(metric.key) ? item.leak : item.value;
 
-  const value = getValue(measure || component);
+  const rawValue = getValue(measure || component);
+  const value = getCCTMeasureValue(metric.key, rawValue);
 
   return (
     <NumericalCell className="sw-py-3">
index 0d90caaf923526f3423fbf2bb5a07cf3f7a9373d..685e3c0d9c67118b88b7020bf4ae6692e856b760 100644 (file)
@@ -34,7 +34,12 @@ import {
   translate,
 } from '../../../helpers/l10n';
 import { MeasureEnhanced } from '../../../types/types';
-import { addMeasureCategories, hasBubbleChart, sortMeasures } from '../utils';
+import {
+  addMeasureCategories,
+  getMetricSubnavigationName,
+  hasBubbleChart,
+  sortMeasures,
+} from '../utils';
 import DomainSubnavigationItem from './DomainSubnavigationItem';
 
 interface Props {
@@ -45,7 +50,7 @@ interface Props {
   showFullMeasures: boolean;
 }
 
-export default function DomainSubnavigation(props: Props) {
+export default function DomainSubnavigation(props: Readonly<Props>) {
   const { domain, onChange, open, selected, showFullMeasures } = props;
   const helperMessageKey = `component_measures.domain_subnavigation.${domain.name}.help`;
   const helper = hasMessage(helperMessageKey) ? translate(helperMessageKey) : undefined;
@@ -100,7 +105,7 @@ export default function DomainSubnavigation(props: Props) {
           <DomainSubnavigationItem
             key={item.metric.key}
             measure={item}
-            name={translateMetric(item.metric)}
+            name={getMetricSubnavigationName(item.metric, translateMetric)}
             onChange={onChange}
             selected={selected}
           />
index a9070e7c98141b05e9baeabf7722e1266ccd7a85..7bd33db86cd162faccf2aaea4604677c5a88aee5 100644 (file)
@@ -29,7 +29,12 @@ interface Props {
   selected: string;
 }
 
-export default function DomainSubnavigationItem({ measure, name, onChange, selected }: Props) {
+export default function DomainSubnavigationItem({
+  measure,
+  name,
+  onChange,
+  selected,
+}: Readonly<Props>) {
   const { key } = measure.metric;
   return (
     <SubnavigationItem active={key === selected} key={key} onClick={onChange} value={key}>
index 67ec7546d9b777e6574b0a7e52e06c6e9e5647fa..d32cb1deea4e0281ac192c9fe0da675eac9be125 100644 (file)
@@ -21,7 +21,6 @@ import { withTheme } from '@emotion/react';
 import styled from '@emotion/styled';
 import {
   BareButton,
-  FlagMessage,
   LAYOUT_FOOTER_HEIGHT,
   LAYOUT_GLOBAL_NAV_HEIGHT,
   LAYOUT_PROJECT_NAV_HEIGHT,
@@ -32,33 +31,24 @@ import {
 } from 'design-system';
 import * as React from 'react';
 import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
-import HelpTooltip from '../../../components/controls/HelpTooltip';
 import { translate } from '../../../helpers/l10n';
 import useFollowScroll from '../../../hooks/useFollowScroll';
-import { isPortfolioLike } from '../../../types/component';
 import { MeasureEnhanced } from '../../../types/types';
-import { PROJECT_OVERVEW, Query, groupByDomains, isProjectOverview } from '../utils';
+import { PROJECT_OVERVEW, Query, isProjectOverview, populateDomainsFromMeasures } from '../utils';
 import DomainSubnavigation from './DomainSubnavigation';
+import { Domain } from '../../../types/measures';
 
 interface Props {
-  canBrowseAllChildProjects: boolean;
   measures: MeasureEnhanced[];
-  qualifier: string;
   selectedMetric: string;
   showFullMeasures: boolean;
   updateQuery: (query: Partial<Query>) => void;
 }
 
-export default function Sidebar(props: Props) {
-  const {
-    showFullMeasures,
-    canBrowseAllChildProjects,
-    qualifier,
-    updateQuery,
-    selectedMetric,
-    measures,
-  } = props;
+export default function Sidebar(props: Readonly<Props>) {
+  const { showFullMeasures, updateQuery, selectedMetric, measures } = props;
   const { top: topScroll, scrolledOnce } = useFollowScroll();
+  const domains = populateDomainsFromMeasures(measures);
 
   const handleChangeMetric = React.useCallback(
     (metric: string) => {
@@ -89,15 +79,6 @@ export default function Sidebar(props: Props) {
           )`,
       }}
     >
-      {!canBrowseAllChildProjects && isPortfolioLike(qualifier) && (
-        <FlagMessage className="sw-mt-4 it__portfolio_warning" variant="warning">
-          {translate('component_measures.not_all_measures_are_shown')}
-          <HelpTooltip
-            className="sw-ml-2"
-            overlay={translate('component_measures.not_all_measures_are_shown.help')}
-          />
-        </FlagMessage>
-      )}
       <section
         className="sw-flex sw-flex-col sw-gap-4 sw-p-4"
         aria-label={translate('component_measures.navigation')}
@@ -118,7 +99,7 @@ export default function Sidebar(props: Props) {
           </SubnavigationItem>
         </SubnavigationGroup>
 
-        {groupByDomains(measures).map((domain: Domain) => (
+        {domains.map((domain: Domain) => (
           <DomainSubnavigation
             domain={domain}
             key={domain.name}
@@ -133,11 +114,6 @@ export default function Sidebar(props: Props) {
   );
 }
 
-interface Domain {
-  measures: MeasureEnhanced[];
-  name: string;
-}
-
 function isDomainSelected(selectedMetric: string, domain: Domain) {
   return (
     selectedMetric === domain.name ||
index 3deb76113b33abc85c79affd25854c2b68d00fc6..7088bd7e63911036da7e315c7304302b5150afb3 100644 (file)
@@ -29,7 +29,7 @@ interface Props {
   measure: MeasureEnhanced;
 }
 
-export default function SubnavigationMeasureValue({ measure }: Props) {
+export default function SubnavigationMeasureValue({ measure }: Readonly<Props>) {
   const isDiff = isDiffMetric(measure.metric.key);
   const value = isDiff ? measure.leak : measure.value;
   const formatted = formatMeasure(value, MetricType.Rating);
index 3d271fe3a6114b0021cdb52971c4323a08d89262..fb947000929bbdc32e7610976533b76c544d549f 100644 (file)
 import { groupBy, memoize, sortBy, toPairs } from 'lodash';
 import { enhanceMeasure } from '../../components/measure/utils';
 import { isBranch, isPullRequest } from '../../helpers/branch-like';
-import { HIDDEN_METRICS } from '../../helpers/constants';
-import { getLocalizedMetricName } from '../../helpers/l10n';
-import { MEASURES_REDIRECTION, getDisplayMetrics, isDiffMetric } from '../../helpers/measures';
+import {
+  CCT_SOFTWARE_QUALITY_METRICS,
+  HIDDEN_METRICS,
+  LEAK_CCT_SOFTWARE_QUALITY_METRICS,
+  LEAK_OLD_TAXONOMY_METRICS,
+  OLD_TAXONOMY_METRICS,
+} from '../../helpers/constants';
+import { getLocalizedMetricName, translate } from '../../helpers/l10n';
+import {
+  MEASURES_REDIRECTION,
+  areLeakCCTMeasuresComputed,
+  areCCTMeasuresComputed,
+  getDisplayMetrics,
+  isDiffMetric,
+  getCCTMeasureValue,
+} from '../../helpers/measures';
 import {
   cleanQuery,
   parseAsOptionalBoolean,
@@ -31,7 +44,7 @@ import {
 } from '../../helpers/query';
 import { BranchLike } from '../../types/branch-like';
 import { ComponentQualifier } from '../../types/component';
-import { MeasurePageView } from '../../types/measures';
+import { Domain, MeasurePageView } from '../../types/measures';
 import { MetricKey, MetricType } from '../../types/metrics';
 import {
   ComponentMeasure,
@@ -51,16 +64,103 @@ export const DEFAULT_VIEW = MeasurePageView.tree;
 export const DEFAULT_METRIC = PROJECT_OVERVEW;
 export const KNOWN_DOMAINS = [
   'Releasability',
-  'Reliability',
   'Security',
-  'SecurityReview',
+  'Reliability',
   'Maintainability',
+  'SecurityReview',
   'Coverage',
   'Duplications',
   'Size',
   'Complexity',
 ];
 
+const CCT_METRIC_DOMAIN_MAP: Dict<string> = {
+  [MetricKey.security_issues]: 'Security',
+  [MetricKey.new_security_issues]: 'Security',
+  [MetricKey.reliability_issues]: 'Reliability',
+  [MetricKey.new_reliability_issues]: 'Reliability',
+  [MetricKey.maintainability_issues]: 'Maintainability',
+  [MetricKey.new_maintainability_issues]: 'Maintainability',
+};
+
+const DEPRECATED_METRICS = [
+  MetricKey.blocker_violations,
+  MetricKey.new_blocker_violations,
+  MetricKey.critical_violations,
+  MetricKey.new_critical_violations,
+  MetricKey.major_violations,
+  MetricKey.new_major_violations,
+  MetricKey.info_violations,
+  MetricKey.new_info_violations,
+  MetricKey.minor_violations,
+  MetricKey.new_minor_violations,
+  MetricKey.high_impact_accepted_issues,
+];
+
+const ISSUES_METRICS = [
+  MetricKey.accepted_issues,
+  MetricKey.new_accepted_issues,
+  MetricKey.confirmed_issues,
+  MetricKey.false_positive_issues,
+  MetricKey.violations,
+  MetricKey.new_violations,
+];
+
+export const populateDomainsFromMeasures = memoize((measures: MeasureEnhanced[]): Domain[] => {
+  let populatedMeasures = measures
+    .filter((measure) => !DEPRECATED_METRICS.includes(measure.metric.key as MetricKey))
+    .map((measure) => {
+      const isDiff = isDiffMetric(measure.metric.key);
+      const calculatedValue = getCCTMeasureValue(
+        measure.metric.key,
+        isDiff ? measure.leak : measure.value,
+      );
+
+      return {
+        ...measure,
+        metric: {
+          ...measure.metric,
+          domain: CCT_METRIC_DOMAIN_MAP[measure.metric.key] ?? measure.metric.domain,
+        },
+        ...(!isDiff && { value: calculatedValue }),
+        ...(isDiff && { leak: calculatedValue }),
+      };
+    });
+  if (areLeakCCTMeasuresComputed(measures)) {
+    populatedMeasures = populatedMeasures.filter(
+      (measure) => !LEAK_OLD_TAXONOMY_METRICS.includes(measure.metric.key as MetricKey),
+    );
+  }
+  if (areCCTMeasuresComputed(measures)) {
+    populatedMeasures = populatedMeasures.filter(
+      (measure) => !OLD_TAXONOMY_METRICS.includes(measure.metric.key as MetricKey),
+    );
+  }
+
+  return groupByDomains(populatedMeasures);
+});
+
+export function getMetricSubnavigationName(
+  metric: Metric,
+  translateFn: (metric: Metric) => string,
+  isDiff = false,
+) {
+  if (
+    [
+      ...LEAK_CCT_SOFTWARE_QUALITY_METRICS,
+      ...CCT_SOFTWARE_QUALITY_METRICS,
+      ...ISSUES_METRICS,
+      ...OLD_TAXONOMY_METRICS,
+      ...LEAK_OLD_TAXONOMY_METRICS,
+    ].includes(metric.key as MetricKey)
+  ) {
+    return translate(
+      `component_measures.metric.${metric.key}.${isDiff ? 'detailed_name' : 'name'}`,
+    );
+  }
+  return translateFn(metric);
+}
+
 export function filterMeasures(measures: MeasureEnhanced[]): MeasureEnhanced[] {
   return measures.filter((measure) => !HIDDEN_METRICS.includes(measure.metric.key as MetricKey));
 }
index 809bc472756477002fda16d01298fe2e758628d5..193034eaaabaa3ab645be67bf9378ca7714bfc1b 100644 (file)
@@ -92,12 +92,24 @@ export const CCT_SOFTWARE_QUALITY_METRICS = [
   MetricKey.maintainability_issues,
 ];
 
+export const LEAK_CCT_SOFTWARE_QUALITY_METRICS = [
+  MetricKey.new_security_issues,
+  MetricKey.new_reliability_issues,
+  MetricKey.new_maintainability_issues,
+];
+
 export const OLD_TAXONOMY_METRICS = [
   MetricKey.vulnerabilities,
   MetricKey.bugs,
   MetricKey.code_smells,
 ];
 
+export const LEAK_OLD_TAXONOMY_METRICS = [
+  MetricKey.new_vulnerabilities,
+  MetricKey.new_bugs,
+  MetricKey.new_code_smells,
+];
+
 export const OLD_TO_NEW_TAXONOMY_METRICS_MAP: { [key in MetricKey]?: MetricKey } = {
   [MetricKey.vulnerabilities]: MetricKey.security_issues,
   [MetricKey.bugs]: MetricKey.reliability_issues,
index f8cef7ad0aeb3a72fc45333377bd9fbdd1ba59fe..3341263961456c2c8a88fc3729d437c292adb592 100644 (file)
@@ -23,7 +23,11 @@ import {
   QualityGateStatusConditionEnhanced,
 } from '../types/quality-gates';
 import { Dict, Measure, MeasureEnhanced, Metric } from '../types/types';
-import { CCT_SOFTWARE_QUALITY_METRICS, ONE_SECOND } from './constants';
+import {
+  CCT_SOFTWARE_QUALITY_METRICS,
+  LEAK_CCT_SOFTWARE_QUALITY_METRICS,
+  ONE_SECOND,
+} from './constants';
 import { translate, translateWithParameters } from './l10n';
 import { getCurrentLocale } from './l10nBundle';
 import { isDefined } from './types';
@@ -72,13 +76,28 @@ export function isDiffMetric(metricKey: MetricKey | string): boolean {
 }
 
 export function getDisplayMetrics(metrics: Metric[]) {
-  return metrics.filter((metric) => !metric.hidden && !['DATA', 'DISTRIB'].includes(metric.type));
+  return metrics.filter(
+    (metric) =>
+      !metric.hidden &&
+      ([...CCT_SOFTWARE_QUALITY_METRICS, ...LEAK_CCT_SOFTWARE_QUALITY_METRICS].includes(
+        metric.key as MetricKey,
+      ) ||
+        ![MetricType.Data, MetricType.Distribution].includes(metric.type as MetricType)),
+  );
 }
 
 export function findMeasure(measures: MeasureEnhanced[], metric: MetricKey | string) {
   return measures.find((measure) => measure.metric.key === metric);
 }
 
+export function areLeakCCTMeasuresComputed(measures?: Measure[] | MeasureEnhanced[]) {
+  return LEAK_CCT_SOFTWARE_QUALITY_METRICS.every((metric) =>
+    measures?.find((measure) =>
+      isMeasureEnhanced(measure) ? measure.metric.key === metric : measure.metric === metric,
+    ),
+  );
+}
+
 export function areCCTMeasuresComputed(measures?: Measure[] | MeasureEnhanced[]) {
   return CCT_SOFTWARE_QUALITY_METRICS.every((metric) =>
     measures?.find((measure) =>
@@ -87,10 +106,26 @@ export function areCCTMeasuresComputed(measures?: Measure[] | MeasureEnhanced[])
   );
 }
 
+export function areLeakAndOverallCCTMeasuresComputed(measures?: Measure[] | MeasureEnhanced[]) {
+  return areLeakCCTMeasuresComputed(measures) && areCCTMeasuresComputed(measures);
+}
+
 function isMeasureEnhanced(measure: Measure | MeasureEnhanced): measure is MeasureEnhanced {
   return (measure.metric as Metric)?.key !== undefined;
 }
 
+export const getCCTMeasureValue = (key: string, value?: string) => {
+  if (
+    CCT_SOFTWARE_QUALITY_METRICS.concat(LEAK_CCT_SOFTWARE_QUALITY_METRICS).includes(
+      key as MetricKey,
+    ) &&
+    value !== undefined
+  ) {
+    return JSON.parse(value).total;
+  }
+  return value;
+};
+
 const HOURS_IN_DAY = 8;
 
 type Formatter = (value: string | number, options?: Dict<unknown>) => string;
index 75a97dcabbdc0da656de6f6d77ce0a6561bbd75e..a909cef1232135a8bf03cd27fead6c46dc60821b 100644 (file)
@@ -912,11 +912,20 @@ export const DEFAULT_METRICS: Dict<Metric> = {
   },
   reliability_issues: {
     key: 'reliability_issues',
-    type: 'INT',
+    type: 'DATA',
     name: 'Reliability',
     description: 'Reliability issues',
-    direction: -1,
-    qualitative: true,
+    direction: 0,
+    qualitative: false,
+    hidden: false,
+  },
+  new_reliability_issues: {
+    key: 'new_reliability_issues',
+    type: 'DATA',
+    name: 'New Reliability',
+    description: 'New Reliability issues',
+    direction: 0,
+    qualitative: false,
     hidden: false,
   },
   reliability_rating: {
@@ -1023,11 +1032,21 @@ export const DEFAULT_METRICS: Dict<Metric> = {
   },
   security_issues: {
     key: 'security_issues',
-    type: 'INT',
+    type: 'DATA',
     name: 'Security',
     description: 'Security issues',
-    direction: -1,
-    qualitative: true,
+    direction: 0,
+    qualitative: false,
+    hidden: false,
+  },
+  new_security_issues: {
+    key: 'new_security_issues',
+    type: 'DATA',
+    name: 'Security',
+    description: 'New Security issues',
+    domain: 'Issues',
+    direction: 0,
+    qualitative: false,
     hidden: false,
   },
   security_rating: {
@@ -1192,11 +1211,22 @@ export const DEFAULT_METRICS: Dict<Metric> = {
   },
   maintainability_issues: {
     key: 'maintainability_issues',
-    type: 'INT',
+    type: 'DATA',
     name: 'Maintainability',
     description: 'Maintainability issues',
-    direction: -1,
-    qualitative: true,
+    domain: 'Issues',
+    direction: 0,
+    qualitative: false,
+    hidden: false,
+  },
+  new_maintainability_issues: {
+    key: 'new_maintainability_issues',
+    type: 'DATA',
+    name: 'Maintainability',
+    description: 'New Maintainability issues',
+    domain: 'Issues',
+    direction: 0,
+    qualitative: false,
     hidden: false,
   },
   sqale_index: {
@@ -1389,4 +1419,14 @@ export const DEFAULT_METRICS: Dict<Metric> = {
     qualitative: false,
     hidden: false,
   },
+  new_accepted_issues: {
+    key: 'new_accepted_issues',
+    type: 'INT',
+    name: 'New Accepted Issues',
+    description: 'New Accepted issues',
+    domain: 'Issues',
+    direction: -1,
+    qualitative: false,
+    hidden: false,
+  },
 };
index 3fb80310940a0f01e12c05d846b23bc7a4b41f5f..57610cf7935166ca14c2edb1d2ef5fa6c13d4551 100644 (file)
@@ -17,7 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { ComponentMeasure, Metric, Period, PeriodMeasure } from './types';
+import { ComponentMeasure, MeasureEnhanced, Metric, Period, PeriodMeasure } from './types';
 
 export interface MeasuresForProjects {
   component: string;
@@ -41,3 +41,8 @@ export enum MeasurePageView {
   tree = 'tree',
   treemap = 'treemap',
 }
+
+export interface Domain {
+  measures: MeasureEnhanced[];
+  name: string;
+}
index c59e1aadaf42b8b8a27348d577b1de17b1d82152..ca94b3dde1ec27992b0e9d25357fa80fcee68515 100644 (file)
@@ -93,15 +93,18 @@ export enum MetricKey {
   new_line_coverage = 'new_line_coverage',
   new_lines = 'new_lines',
   new_lines_to_cover = 'new_lines_to_cover',
+  new_maintainability_issues = 'new_maintainability_issues',
   new_maintainability_rating = 'new_maintainability_rating',
   new_maintainability_rating_distribution = 'new_maintainability_rating_distribution',
   new_major_violations = 'new_major_violations',
   new_minor_violations = 'new_minor_violations',
+  new_reliability_issues = 'new_reliability_issues',
   new_reliability_rating = 'new_reliability_rating',
   new_reliability_remediation_effort = 'new_reliability_remediation_effort',
   new_reliability_rating_distribution = 'new_reliability_rating_distribution',
   new_security_hotspots = 'new_security_hotspots',
   new_security_hotspots_reviewed = 'new_security_hotspots_reviewed',
+  new_security_issues = 'new_security_issues',
   new_security_rating = 'new_security_rating',
   new_security_rating_distribution = 'new_security_rating_distribution',
   new_security_remediation_effort = 'new_security_remediation_effort',
index b92c78759adeced31b0aaf3ed96dc98726937bd6..7f39eea439aa4e6b6cec5135c7dab595d9d91ff0 100644 (file)
@@ -4180,6 +4180,33 @@ component_measures.bubble_chart.zoom_level=Current zoom level. Scroll on the cha
 component_measures.not_all_measures_are_shown=Not all projects and applications are included
 component_measures.not_all_measures_are_shown.help=You do not have access to all projects and/or applications. Measures are still computed based on all projects and applications.
 
+component_measures.metric.new_security_issues.name=Issues
+component_measures.metric.new_security_issues.detailed_name=New Issues
+component_measures.metric.new_vulnerabilities.name=Issues
+component_measures.metric.new_vulnerabilities.detailed_name=New Issues
+component_measures.metric.new_reliability_issues.name=Issues
+component_measures.metric.new_reliability_issues.detailed_name=New Issues
+component_measures.metric.new_maintainability_issues.name=Issues
+component_measures.metric.new_maintainability_issues.detailed_name=New Issues
+component_measures.metric.new_code_smells.name=Issues
+component_measures.metric.new_code_smells.detailed_name=New Issues
+component_measures.metric.new_violations.name=Open Issues
+component_measures.metric.new_violations.detailed_name=New Open Issues
+component_measures.metric.new_accepted_issues.name=Accepted Issues
+component_measures.metric.new_accepted_issues.detailed_name=New Accepted Issues
+component_measures.metric.new_bugs.name=Issues
+component_measures.metric.new_bugs.detailed_name=New Issues
+component_measures.metric.security_issues.name=Issues
+component_measures.metric.vulnerabilities.name=Issues
+component_measures.metric.reliability_issues.name=Issues
+component_measures.metric.bugs.name=Issues
+component_measures.metric.maintainability_issues.name=Issues
+component_measures.metric.code_smells.name=Issues
+component_measures.metric.violations.name=Open Issues
+component_measures.metric.accepted_issues.name=Accepted Issues
+component_measures.metric.confirmed_issues.name=Confirmed Issues
+component_measures.metric.false_positive_issues.name=False Positive Issues
+
 #------------------------------------------------------------------------------
 #
 # DOCS