From dd0e5a92ebe20d6070f44881de547a954f8e4f48 Mon Sep 17 00:00:00 2001 From: stanislavh Date: Fri, 1 Nov 2024 17:07:08 +0100 Subject: [PATCH] SONAR-23299 Failed condition links point to issues list --- .../branches/QualityGateCondition.tsx | 32 +++- .../QualityGatePanelSection-test.tsx | 156 +++++++++++++----- .../src/main/js/apps/overview/utils.tsx | 9 +- .../components/shared/__tests__/utils-test.ts | 51 ++++++ .../src/main/js/components/shared/utils.ts | 53 ++++++ 5 files changed, 259 insertions(+), 42 deletions(-) diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx index 2cc6a4e6c7f..21d0f73e0a9 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx @@ -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 { 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 { [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]) { diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGatePanelSection-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGatePanelSection-test.tsx index c36387e051b..e9f80e0bbbd 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGatePanelSection-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGatePanelSection-test.tsx @@ -21,55 +21,32 @@ 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 = {}, currentUser: CurrentUser = mockLoggedInUser(), diff --git a/server/sonar-web/src/main/js/apps/overview/utils.tsx b/server/sonar-web/src/main/js/apps/overview/utils.tsx index 4fb159fbfb6..424628558b3 100644 --- a/server/sonar-web/src/main/js/apps/overview/utils.tsx +++ b/server/sonar-web/src/main/js/apps/overview/utils.tsx @@ -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 = { [MetricKey.reliability_rating]: IssueType.Bug, [MetricKey.new_reliability_rating]: IssueType.Bug, diff --git a/server/sonar-web/src/main/js/components/shared/__tests__/utils-test.ts b/server/sonar-web/src/main/js/components/shared/__tests__/utils-test.ts index 54303ef1ce7..a2d00d503b6 100644 --- a/server/sonar-web/src/main/js/components/shared/__tests__/utils-test.ts +++ b/server/sonar-web/src/main/js/components/shared/__tests__/utils-test.ts @@ -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, + }); + }); }); diff --git a/server/sonar-web/src/main/js/components/shared/utils.ts b/server/sonar-web/src/main/js/components/shared/utils.ts index 5726c655dcf..b0bef60343a 100644 --- a/server/sonar-web/src/main/js/components/shared/utils.ts +++ b/server/sonar-web/src/main/js/components/shared/utils.ts @@ -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> = { [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) { -- 2.39.5