]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-23299 Failed condition links point to issues list
authorstanislavh <stanislav.honcharov@sonarsource.com>
Fri, 1 Nov 2024 16:07:08 +0000 (17:07 +0100)
committersonartech <sonartech@sonarsource.com>
Tue, 5 Nov 2024 20:03:03 +0000 (20:03 +0000)
server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx
server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGatePanelSection-test.tsx
server/sonar-web/src/main/js/apps/overview/utils.tsx
server/sonar-web/src/main/js/components/shared/__tests__/utils-test.ts
server/sonar-web/src/main/js/components/shared/utils.ts

index 2cc6a4e6c7fca58c378bfe95afd6e5afe6a17274..21d0f73e0a92576a733ace7310da2e47d40c3baf 100644 (file)
@@ -40,11 +40,12 @@ import {
 import { getOperatorLabel } from '../../../helpers/qualityGates';
 import { getComponentDrilldownUrl } from '../../../helpers/urls';
 import { BranchLike } from '../../../types/branch-like';
+import { SoftwareQuality } from '../../../types/clean-code-taxonomy';
 import { IssueType } from '../../../types/issues';
 import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
 import { Component, Dict, Metric } from '../../../types/types';
 import { getLocalizedMetricNameNoDiffMetric } from '../../quality-gates/utils';
-import { RATING_TO_SEVERITIES_MAPPING } from '../utils';
+import { MQR_RATING_TO_SEVERITIES_MAPPING, RATING_TO_SEVERITIES_MAPPING } from '../utils';
 
 interface Props {
   branchLike?: BranchLike;
@@ -78,6 +79,22 @@ export class QualityGateCondition extends React.PureComponent<Props> {
     return this.getIssuesUrl(inNewCodePeriod, { types: 'CODE_SMELL' });
   }
 
+  getUrlForSoftwareQualityRatings(quality: SoftwareQuality, isNewCode: boolean) {
+    const { condition } = this.props;
+    const threshold = condition.level === 'ERROR' ? condition.error : condition.warning;
+
+    if (quality === SoftwareQuality.Maintainability) {
+      return this.getIssuesUrl(isNewCode, {
+        impactSoftwareQualities: quality,
+      });
+    }
+
+    return this.getIssuesUrl(isNewCode, {
+      impactSeverities: MQR_RATING_TO_SEVERITIES_MAPPING[Number(threshold) - 1],
+      impactSoftwareQualities: quality,
+    });
+  }
+
   getUrlForBugsOrVulnerabilities(type: string, inNewCodePeriod: boolean) {
     const { condition } = this.props;
     const threshold = condition.level === 'ERROR' ? condition.error : condition.warning;
@@ -106,6 +123,19 @@ export class QualityGateCondition extends React.PureComponent<Props> {
       [MetricKey.new_maintainability_rating]: () => this.getUrlForCodeSmells(true),
       [MetricKey.security_hotspots_reviewed]: () => this.getUrlForSecurityHotspot(false),
       [MetricKey.new_security_hotspots_reviewed]: () => this.getUrlForSecurityHotspot(true),
+      // MQR
+      [MetricKey.new_software_quality_reliability_rating]: () =>
+        this.getUrlForSoftwareQualityRatings(SoftwareQuality.Reliability, true),
+      [MetricKey.new_software_quality_security_rating]: () =>
+        this.getUrlForSoftwareQualityRatings(SoftwareQuality.Security, true),
+      [MetricKey.new_software_quality_maintainability_rating]: () =>
+        this.getUrlForSoftwareQualityRatings(SoftwareQuality.Maintainability, true),
+      [MetricKey.software_quality_reliability_rating]: () =>
+        this.getUrlForSoftwareQualityRatings(SoftwareQuality.Reliability, false),
+      [MetricKey.software_quality_security_rating]: () =>
+        this.getUrlForSoftwareQualityRatings(SoftwareQuality.Security, false),
+      [MetricKey.software_quality_maintainability_rating]: () =>
+        this.getUrlForSoftwareQualityRatings(SoftwareQuality.Maintainability, false),
     };
 
     if (METRICS_TO_URL_MAPPING[metricKey]) {
index c36387e051b4959c6d5f96deb78a42282d7187f5..e9f80e0bbbdd565b82ca6096bb10d417ef167b1b 100644 (file)
 import { screen } from '@testing-library/react';
 import { byRole } from '~sonar-aligned/helpers/testSelector';
 import { Status } from '~sonar-aligned/types/common';
-import { MetricKey } from '~sonar-aligned/types/metrics';
+import { MetricKey, MetricType } from '~sonar-aligned/types/metrics';
 import CurrentUserContextProvider from '../../../../app/components/current-user/CurrentUserContextProvider';
-import { mockQualityGate, mockQualityGateStatus } from '../../../../helpers/mocks/quality-gates';
+import {
+  mockQualityGate,
+  mockQualityGateStatus,
+  mockQualityGateStatusConditionEnhanced,
+} from '../../../../helpers/mocks/quality-gates';
 import { mockLoggedInUser } from '../../../../helpers/testMocks';
 import { renderComponent } from '../../../../helpers/testReactTestingUtils';
 import { CaycStatus } from '../../../../types/types';
 import { CurrentUser, NoticeType } from '../../../../types/users';
 import QualityGatePanelSection, { QualityGatePanelSectionProps } from '../QualityGatePanelSection';
 
-const failedConditions = [
-  {
-    level: 'ERROR' as Status,
+const mockCondition = (metric: MetricKey, type = MetricType.Rating) =>
+  mockQualityGateStatusConditionEnhanced({
+    level: 'ERROR',
+    metric,
     measure: {
-      metric: {
-        id: 'metricId1',
-        key: MetricKey.new_coverage,
-        name: 'metricName1',
-        type: 'metricType1',
-      },
+      metric: { id: metric, key: metric, name: metric, type },
     },
-    metric: MetricKey.new_coverage,
-    op: 'op1',
-  },
-  {
-    level: 'ERROR' as Status,
-    measure: {
-      metric: {
-        id: 'metricId2',
-        key: MetricKey.security_hotspots,
-        name: 'metricName2',
-        type: 'metricType2',
-      },
-    },
-    metric: MetricKey.security_hotspots,
-    op: 'op2',
-  },
-  {
-    level: 'ERROR' as Status,
-    measure: {
-      metric: {
-        id: 'metricId2',
-        key: MetricKey.new_violations,
-        name: 'metricName2',
-        type: 'metricType2',
-      },
-    },
-    metric: MetricKey.new_violations,
-    op: 'op2',
-  },
+  });
+
+const failedConditions = [
+  mockCondition(MetricKey.new_coverage),
+  mockCondition(MetricKey.security_hotspots),
+  mockCondition(MetricKey.new_violations),
 ];
 
 const qgStatus = mockQualityGateStatus({
@@ -116,6 +93,105 @@ it('should not render 0 New issues onboarding for user who dismissed it', async
   expect(await byRole('alertdialog').query()).not.toBeInTheDocument();
 });
 
+it('should render correct links for ratings with "overall code" failed conditions', () => {
+  renderQualityGatePanelSection(
+    {
+      isApplication: false,
+      isNewCode: false,
+      qgStatus: {
+        ...qgStatus,
+        failedConditions: [
+          mockCondition(MetricKey.sqale_rating),
+          mockCondition(MetricKey.reliability_rating),
+          mockCondition(MetricKey.security_rating),
+          mockCondition(MetricKey.software_quality_security_rating),
+          mockCondition(MetricKey.software_quality_reliability_rating),
+          mockCondition(MetricKey.software_quality_maintainability_rating),
+        ],
+      },
+      qualityGate: mockQualityGate({ isBuiltIn: true }),
+    },
+    mockLoggedInUser({
+      dismissedNotices: { [NoticeType.OVERVIEW_ZERO_NEW_ISSUES_SIMPLIFICATION]: true },
+    }),
+  );
+
+  expect(byRole('link', { name: /sqale_rating/ }).get()).toHaveAttribute(
+    'href',
+    '/project/issues?issueStatuses=OPEN%2CCONFIRMED&types=CODE_SMELL&id=qgStatusKey',
+  );
+  expect(byRole('link', { name: /reliability_rating/ }).getAt(0)).toHaveAttribute(
+    'href',
+    '/project/issues?issueStatuses=OPEN%2CCONFIRMED&types=BUG&id=qgStatusKey',
+  );
+  expect(byRole('link', { name: /security_rating/ }).getAt(0)).toHaveAttribute(
+    'href',
+    '/project/issues?issueStatuses=OPEN%2CCONFIRMED&types=VULNERABILITY&id=qgStatusKey',
+  );
+  expect(byRole('link', { name: /software_quality_security_rating/ }).get()).toHaveAttribute(
+    'href',
+    '/project/issues?issueStatuses=OPEN%2CCONFIRMED&impactSoftwareQualities=SECURITY&id=qgStatusKey',
+  );
+  expect(byRole('link', { name: /software_quality_reliability_rating/ }).get()).toHaveAttribute(
+    'href',
+    '/project/issues?issueStatuses=OPEN%2CCONFIRMED&impactSoftwareQualities=RELIABILITY&id=qgStatusKey',
+  );
+  expect(byRole('link', { name: /software_quality_maintainability_rating/ }).get()).toHaveAttribute(
+    'href',
+    '/project/issues?issueStatuses=OPEN%2CCONFIRMED&impactSoftwareQualities=MAINTAINABILITY&id=qgStatusKey',
+  );
+});
+
+it('should render correct links for ratings with "new code" failed conditions', () => {
+  renderQualityGatePanelSection(
+    {
+      isApplication: false,
+      qgStatus: {
+        ...qgStatus,
+        failedConditions: [
+          mockCondition(MetricKey.new_maintainability_rating),
+          mockCondition(MetricKey.new_security_rating),
+          mockCondition(MetricKey.new_reliability_rating),
+          mockCondition(MetricKey.new_software_quality_security_rating),
+          mockCondition(MetricKey.new_software_quality_reliability_rating),
+          mockCondition(MetricKey.new_software_quality_maintainability_rating),
+        ],
+      },
+      qualityGate: mockQualityGate({ isBuiltIn: true }),
+    },
+    mockLoggedInUser({
+      dismissedNotices: { [NoticeType.OVERVIEW_ZERO_NEW_ISSUES_SIMPLIFICATION]: true },
+    }),
+  );
+
+  expect(byRole('link', { name: /new_maintainability_rating/ }).get()).toHaveAttribute(
+    'href',
+    '/project/issues?issueStatuses=OPEN%2CCONFIRMED&types=CODE_SMELL&inNewCodePeriod=true&id=qgStatusKey',
+  );
+  expect(byRole('link', { name: /new_security_rating/ }).get()).toHaveAttribute(
+    'href',
+    '/project/issues?issueStatuses=OPEN%2CCONFIRMED&types=VULNERABILITY&inNewCodePeriod=true&id=qgStatusKey',
+  );
+  expect(byRole('link', { name: /new_reliability_rating/ }).get()).toHaveAttribute(
+    'href',
+    '/project/issues?issueStatuses=OPEN%2CCONFIRMED&types=BUG&inNewCodePeriod=true&id=qgStatusKey',
+  );
+  expect(byRole('link', { name: /new_software_quality_security_rating/ }).get()).toHaveAttribute(
+    'href',
+    '/project/issues?issueStatuses=OPEN%2CCONFIRMED&impactSoftwareQualities=SECURITY&inNewCodePeriod=true&id=qgStatusKey',
+  );
+  expect(byRole('link', { name: /new_software_quality_reliability_rating/ }).get()).toHaveAttribute(
+    'href',
+    '/project/issues?issueStatuses=OPEN%2CCONFIRMED&impactSoftwareQualities=RELIABILITY&inNewCodePeriod=true&id=qgStatusKey',
+  );
+  expect(
+    byRole('link', { name: /new_software_quality_maintainability_rating/ }).get(),
+  ).toHaveAttribute(
+    'href',
+    '/project/issues?issueStatuses=OPEN%2CCONFIRMED&impactSoftwareQualities=MAINTAINABILITY&inNewCodePeriod=true&id=qgStatusKey',
+  );
+});
+
 function renderQualityGatePanelSection(
   props: Partial<QualityGatePanelSectionProps> = {},
   currentUser: CurrentUser = mockLoggedInUser(),
index 4fb159fbfb6a5fb80f5ec2e92cf5562c63560a3d..424628558b3e1d1e0a70fcdd30d408f68f7fbbca 100644 (file)
@@ -26,7 +26,7 @@ import { RawQuery } from '~sonar-aligned/types/router';
 import { ISSUETYPE_METRIC_KEYS_MAP } from '../../helpers/issues';
 import { translate } from '../../helpers/l10n';
 import { parseAsString } from '../../helpers/query';
-import { SoftwareQuality } from '../../types/clean-code-taxonomy';
+import { SoftwareImpactSeverity, SoftwareQuality } from '../../types/clean-code-taxonomy';
 import { IssueType } from '../../types/issues';
 import { AnalysisMeasuresVariations, MeasureHistory } from '../../types/project-activity';
 import { QualityGateStatusConditionEnhanced } from '../../types/quality-gates';
@@ -167,6 +167,13 @@ export const RATING_TO_SEVERITIES_MAPPING = [
   'BLOCKER',
 ];
 
+export const MQR_RATING_TO_SEVERITIES_MAPPING = [
+  `${SoftwareImpactSeverity.Blocker},${SoftwareImpactSeverity.High},${SoftwareImpactSeverity.Medium},${SoftwareImpactSeverity.Info}`,
+  `${SoftwareImpactSeverity.Blocker},${SoftwareImpactSeverity.High},${SoftwareImpactSeverity.Medium}`,
+  `${SoftwareImpactSeverity.Blocker},${SoftwareImpactSeverity.High}`,
+  `${SoftwareImpactSeverity.Blocker}`,
+];
+
 export const RATING_METRICS_MAPPING: Dict<IssueType> = {
   [MetricKey.reliability_rating]: IssueType.Bug,
   [MetricKey.new_reliability_rating]: IssueType.Bug,
index 54303ef1ce7eaa333d0a18f4993d08a1f6f16d10..a2d00d503b62436414c36dc94daf7d83d1fb5467 100644 (file)
@@ -19,6 +19,7 @@
  */
 
 import { MetricKey } from '~sonar-aligned/types/metrics';
+import { SoftwareImpactSeverity, SoftwareQuality } from '../../../types/clean-code-taxonomy';
 import { propsToIssueParams } from '../utils';
 
 describe('propsToIssueParams', () => {
@@ -32,4 +33,54 @@ describe('propsToIssueParams', () => {
       issueStatuses: 'FALSE_POSITIVE',
     });
   });
+  it.each([
+    [MetricKey.software_quality_info_issues, { impactSeverities: SoftwareImpactSeverity.Info }],
+    [MetricKey.software_quality_low_issues, { impactSeverities: SoftwareImpactSeverity.Low }],
+    [MetricKey.software_quality_medium_issues, { impactSeverities: SoftwareImpactSeverity.Medium }],
+    [MetricKey.software_quality_high_issues, { impactSeverities: SoftwareImpactSeverity.High }],
+    [
+      MetricKey.software_quality_blocker_issues,
+      { impactSeverities: SoftwareImpactSeverity.Blocker },
+    ],
+    [MetricKey.new_software_quality_info_issues, { impactSeverities: SoftwareImpactSeverity.Info }],
+    [MetricKey.new_software_quality_low_issues, { impactSeverities: SoftwareImpactSeverity.Low }],
+    [
+      MetricKey.new_software_quality_medium_issues,
+      { impactSeverities: SoftwareImpactSeverity.Medium },
+    ],
+    [MetricKey.new_software_quality_high_issues, { impactSeverities: SoftwareImpactSeverity.High }],
+    [
+      MetricKey.new_software_quality_blocker_issues,
+      { impactSeverities: SoftwareImpactSeverity.Blocker },
+    ],
+    [
+      MetricKey.software_quality_reliability_issues,
+      { impactSoftwareQualities: SoftwareQuality.Reliability },
+    ],
+    [
+      MetricKey.software_quality_maintainability_issues,
+      { impactSoftwareQualities: SoftwareQuality.Maintainability },
+    ],
+    [
+      MetricKey.software_quality_security_issues,
+      { impactSoftwareQualities: SoftwareQuality.Security },
+    ],
+    [
+      MetricKey.new_software_quality_reliability_issues,
+      { impactSoftwareQualities: SoftwareQuality.Reliability },
+    ],
+    [
+      MetricKey.new_software_quality_maintainability_issues,
+      { impactSoftwareQualities: SoftwareQuality.Maintainability },
+    ],
+    [
+      MetricKey.new_software_quality_security_issues,
+      { impactSoftwareQualities: SoftwareQuality.Security },
+    ],
+  ])(`should render correct params for %s`, (metricKey, result) => {
+    expect(propsToIssueParams(metricKey)).toEqual({
+      issueStatuses: 'OPEN,CONFIRMED',
+      ...result,
+    });
+  });
 });
index 5726c655dcf0f62ea81223bfbc04f5579c97afc2..b0bef60343a42dc67cb05c72d86493ef7b84b38d 100644 (file)
@@ -19,6 +19,7 @@
  */
 
 import { MetricKey } from '~sonar-aligned/types/metrics';
+import { SoftwareImpactSeverity, SoftwareQuality } from '../../types/clean-code-taxonomy';
 import { IssueStatus } from '../../types/issues';
 import { Dict } from '../../types/types';
 
@@ -46,6 +47,25 @@ const ISSUE_MEASURES = [
   MetricKey.vulnerabilities,
   MetricKey.new_vulnerabilities,
   MetricKey.prioritized_rule_issues,
+  // MQR
+  MetricKey.new_software_quality_info_issues,
+  MetricKey.new_software_quality_low_issues,
+  MetricKey.new_software_quality_medium_issues,
+  MetricKey.new_software_quality_high_issues,
+  MetricKey.new_software_quality_blocker_issues,
+  MetricKey.software_quality_info_issues,
+  MetricKey.software_quality_low_issues,
+  MetricKey.software_quality_medium_issues,
+  MetricKey.software_quality_high_issues,
+  MetricKey.software_quality_blocker_issues,
+  MetricKey.new_software_quality_maintainability_issues,
+  MetricKey.new_software_quality_reliability_issues,
+  MetricKey.new_software_quality_security_issues,
+  MetricKey.software_quality_maintainability_issues,
+  MetricKey.software_quality_reliability_issues,
+  MetricKey.software_quality_security_issues,
+  MetricKey.new_software_quality_maintainability_rating,
+  MetricKey.software_quality_maintainability_rating,
 ];
 
 export const DEFAULT_ISSUES_QUERY = {
@@ -74,6 +94,39 @@ const issueParamsPerMetric: Dict<Dict<string>> = {
   [MetricKey.vulnerabilities]: { types: 'VULNERABILITY' },
   [MetricKey.new_vulnerabilities]: { types: 'VULNERABILITY' },
   [MetricKey.prioritized_rule_issues]: { prioritizedRule: 'true' },
+  // MQR
+  [MetricKey.new_software_quality_info_issues]: { impactSeverities: SoftwareImpactSeverity.Info },
+  [MetricKey.new_software_quality_low_issues]: { impactSeverities: SoftwareImpactSeverity.Low },
+  [MetricKey.new_software_quality_medium_issues]: {
+    impactSeverities: SoftwareImpactSeverity.Medium,
+  },
+  [MetricKey.new_software_quality_high_issues]: { impactSeverities: SoftwareImpactSeverity.High },
+  [MetricKey.new_software_quality_blocker_issues]: {
+    impactSeverities: SoftwareImpactSeverity.Blocker,
+  },
+  [MetricKey.software_quality_info_issues]: { impactSeverities: SoftwareImpactSeverity.Info },
+  [MetricKey.software_quality_low_issues]: { impactSeverities: SoftwareImpactSeverity.Low },
+  [MetricKey.software_quality_medium_issues]: { impactSeverities: SoftwareImpactSeverity.Medium },
+  [MetricKey.software_quality_high_issues]: { impactSeverities: SoftwareImpactSeverity.High },
+  [MetricKey.software_quality_blocker_issues]: { impactSeverities: SoftwareImpactSeverity.Blocker },
+  [MetricKey.new_software_quality_maintainability_issues]: {
+    impactSoftwareQualities: SoftwareQuality.Maintainability,
+  },
+  [MetricKey.new_software_quality_reliability_issues]: {
+    impactSoftwareQualities: SoftwareQuality.Reliability,
+  },
+  [MetricKey.new_software_quality_security_issues]: {
+    impactSoftwareQualities: SoftwareQuality.Security,
+  },
+  [MetricKey.software_quality_maintainability_issues]: {
+    impactSoftwareQualities: SoftwareQuality.Maintainability,
+  },
+  [MetricKey.software_quality_reliability_issues]: {
+    impactSoftwareQualities: SoftwareQuality.Reliability,
+  },
+  [MetricKey.software_quality_security_issues]: {
+    impactSoftwareQualities: SoftwareQuality.Security,
+  },
 };
 
 export function isIssueMeasure(metric: string) {