From 99f771c79563dcb26fbf71cee8a6da2deab7ee8f Mon Sep 17 00:00:00 2001 From: Jay Date: Fri, 8 Dec 2023 17:09:43 +0100 Subject: [PATCH] SONAR-21215 Update PR Overview UI --- .../BranchQualityGateConditions.tsx | 21 +++++-- .../__tests__/BranchQualityGate-it.tsx | 8 +-- .../MeasuresCard.tsx | 29 ++++------ .../MeasuresCardNumber.tsx | 54 +++++++++++------- .../MeasuresCardPanel.tsx | 35 ++++-------- .../MeasuresCardPercent.tsx | 55 +++++++++---------- .../pullRequests/PullRequestOverview.tsx | 24 ++++---- .../__tests__/PullRequestOverview-it.tsx | 4 +- .../resources/org/sonar/l10n/core.properties | 8 ++- 9 files changed, 118 insertions(+), 120 deletions(-) rename server/sonar-web/src/main/js/apps/overview/{branches => pullRequests}/MeasuresCard.tsx (80%) rename server/sonar-web/src/main/js/apps/overview/{branches => pullRequests}/MeasuresCardNumber.tsx (56%) rename server/sonar-web/src/main/js/apps/overview/{branches => pullRequests}/MeasuresCardPanel.tsx (81%) rename server/sonar-web/src/main/js/apps/overview/{branches => pullRequests}/MeasuresCardPercent.tsx (81%) diff --git a/server/sonar-web/src/main/js/apps/overview/components/BranchQualityGateConditions.tsx b/server/sonar-web/src/main/js/apps/overview/components/BranchQualityGateConditions.tsx index 22b53b8a9ee..9d0759ccf10 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/BranchQualityGateConditions.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/BranchQualityGateConditions.tsx @@ -18,7 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { ChevronRightIcon, DangerButtonSecondary } from 'design-system'; +import styled from '@emotion/styled'; +import { Badge, ButtonSecondary, themeColor } from 'design-system'; import React from 'react'; import { useIntl } from 'react-intl'; import { @@ -27,7 +28,7 @@ import { propsToIssueParams, } from '../../../components/shared/utils'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; -import { getLocalizedMetricName } from '../../../helpers/l10n'; +import { getLocalizedMetricName, translate } from '../../../helpers/l10n'; import { formatMeasure, getShortType, isDiffMetric } from '../../../helpers/measures'; import { getComponentDrilldownUrl, @@ -78,10 +79,14 @@ function FailedQGCondition( const url = getQGConditionUrl(component.key, condition, branchLike); return ( - - - - + + + {translate('overview.measures.failed_badge')} + + + + + ); } @@ -209,3 +214,7 @@ function getQGConditionUrl( listView: true, }); } + +const SpanDanger = styled.span` + color: ${themeColor('danger')}; +`; diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/BranchQualityGate-it.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/BranchQualityGate-it.tsx index 1731d8793db..5bbb4be19c4 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/BranchQualityGate-it.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/BranchQualityGate-it.tsx @@ -34,7 +34,7 @@ it('renders failed QG', () => { // Maintainability rating condition const maintainabilityRatingLink = byRole('link', { - name: 'overview.failed_condition.x_rating_requiredmetric_domain.Maintainability metric.type.ratingE A', + name: 'overview.measures.failed_badge overview.failed_condition.x_rating_requiredmetric_domain.Maintainability metric.type.ratingE A', }).get(); expect(maintainabilityRatingLink).toBeInTheDocument(); expect(maintainabilityRatingLink).toHaveAttribute( @@ -44,7 +44,7 @@ it('renders failed QG', () => { // Security Hotspots rating condition const securityHotspotsRatingLink = byRole('link', { - name: 'overview.failed_condition.x_rating_requiredmetric_domain.Security Review metric.type.ratingE A', + name: 'overview.measures.failed_badge overview.failed_condition.x_rating_requiredmetric_domain.Security Review metric.type.ratingE A', }).get(); expect(securityHotspotsRatingLink).toBeInTheDocument(); expect(securityHotspotsRatingLink).toHaveAttribute( @@ -54,7 +54,7 @@ it('renders failed QG', () => { // New code smells const codeSmellsLink = byRole('link', { - name: 'overview.failed_condition.x_required 5 Code Smells ≤ 1', + name: 'overview.measures.failed_badge overview.failed_condition.x_required 5 Code Smells ≤ 1', }).get(); expect(codeSmellsLink).toBeInTheDocument(); expect(codeSmellsLink).toHaveAttribute( @@ -64,7 +64,7 @@ it('renders failed QG', () => { // Conditions to cover const conditionToCoverLink = byRole('link', { - name: 'overview.failed_condition.x_required 5 Conditions to cover ≥ 10', + name: 'overview.measures.failed_badge overview.failed_condition.x_required 5 Conditions to cover ≥ 10', }).get(); expect(conditionToCoverLink).toBeInTheDocument(); expect(conditionToCoverLink).toHaveAttribute( diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresCard.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCard.tsx similarity index 80% rename from server/sonar-web/src/main/js/apps/overview/branches/MeasuresCard.tsx rename to server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCard.tsx index 7539c92037a..ae195fceafd 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresCard.tsx +++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCard.tsx @@ -18,8 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import styled from '@emotion/styled'; -import classNames from 'classnames'; -import { Card, ContentLink, PageContentFontWrapper, themeColor } from 'design-system'; +import { Badge, Card, ContentLink, themeColor } from 'design-system'; import * as React from 'react'; import { To } from 'react-router-dom'; import { translate, translateWithParameters } from '../../../helpers/l10n'; @@ -41,16 +40,11 @@ export default function MeasuresCard( const { failed, children, metric, icon, value, url, label, ...rest } = props; return ( - - +
{value ? ( — )} {translate(label)} + {failed && ( + + {translate('overview.measures.failed_badge')} + + )}
{children &&
{children}
} - +
{icon &&
{icon}
} -
+ ); } const StyledNoValue = styled.span` color: ${themeColor('pageTitle')}; `; - -export const StyledCard = styled(Card)` - &.failed { - border-color: ${themeColor('qgCardFailed')}; - } -`; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresCardNumber.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCardNumber.tsx similarity index 56% rename from server/sonar-web/src/main/js/apps/overview/branches/MeasuresCardNumber.tsx rename to server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCardNumber.tsx index 340dc2863c9..7f905c97b9e 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresCardNumber.tsx +++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCardNumber.tsx @@ -17,53 +17,65 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { TextError } from 'design-system'; +import { LightLabel, TextError } from 'design-system'; import * as React from 'react'; +import { useIntl } from 'react-intl'; import { To } from 'react-router-dom'; import { formatMeasure } from '../../../helpers/measures'; import { MetricKey, MetricType } from '../../../types/metrics'; import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates'; +import { Status } from '../utils'; import MeasuresCard from './MeasuresCard'; interface Props { - failedConditions: QualityGateStatusConditionEnhanced[]; + conditions: QualityGateStatusConditionEnhanced[]; label: string; url: To; value: string; - failingConditionMetric: MetricKey; - requireLabel: string; + conditionMetric: MetricKey; guidingKeyOnError?: string; } export default function MeasuresCardNumber( props: React.PropsWithChildren>, ) { - const { - label, - value, - failedConditions, - url, - failingConditionMetric, - requireLabel, - guidingKeyOnError, - ...rest - } = props; + const { label, value, conditions, url, conditionMetric, guidingKeyOnError, ...rest } = props; - const failed = Boolean( - failedConditions.find((condition) => condition.metric === failingConditionMetric), - ); + const intl = useIntl(); + + const condition = conditions.find((condition) => condition.metric === conditionMetric); + + const conditionFailed = condition?.level === Status.ERROR; + + const requireLabel = + condition && + intl.formatMessage( + { id: 'overview.quality_gate.required_x' }, + { + operator: condition.op === 'GT' ? '≤' : '≥', + value: formatMeasure(condition.error, MetricType.Percent, { + decimals: 2, + omitExtraDecimalZeros: true, + }), + }, + ); return ( - {failed && } + {requireLabel && + (conditionFailed ? ( + + ) : ( + {requireLabel} + ))} ); } diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresCardPanel.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCardPanel.tsx similarity index 81% rename from server/sonar-web/src/main/js/apps/overview/branches/MeasuresCardPanel.tsx rename to server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCardPanel.tsx index a909f2d2ed2..5f116e4c822 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresCardPanel.tsx +++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCardPanel.tsx @@ -19,7 +19,6 @@ */ import classNames from 'classnames'; import * as React from 'react'; -import { useIntl } from 'react-intl'; import { getLeakValue } from '../../../components/measure/utils'; import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; @@ -42,13 +41,11 @@ interface Props { branchLike?: BranchLike; component: Component; measures: MeasureEnhanced[]; - failedConditions: QualityGateStatusConditionEnhanced[]; + conditions: QualityGateStatusConditionEnhanced[]; } export default function MeasuresCardPanel(props: React.PropsWithChildren) { - const { branchLike, component, measures, failedConditions, className } = props; - - const intl = useIntl(); + const { branchLike, component, measures, conditions, className } = props; const newViolations = getLeakValue(findMeasure(measures, MetricKey.new_violations)) as string; const newSecurityHotspots = getLeakValue( @@ -66,14 +63,8 @@ export default function MeasuresCardPanel(props: React.PropsWithChildren) ...DEFAULT_ISSUES_QUERY, })} value={newViolations} - failedConditions={failedConditions} - failingConditionMetric={MetricKey.new_violations} - requireLabel={intl.formatMessage( - { id: 'overview.quality_gate.require_fixing' }, - { - count: newViolations, - }, - )} + conditions={conditions} + conditionMetric={MetricKey.new_violations} guidingKeyOnError="overviewZeroNewIssuesSimplification" /> @@ -88,8 +79,8 @@ export default function MeasuresCardPanel(props: React.PropsWithChildren) branchLike, listView: true, })} - failedConditions={failedConditions} - failingConditionMetric={MetricKey.new_coverage} + conditions={conditions} + conditionMetric={MetricKey.new_coverage} newLinesMetric={MetricKey.new_lines_to_cover} afterMergeMetric={MetricKey.coverage} measures={measures} @@ -107,14 +98,8 @@ export default function MeasuresCardPanel(props: React.PropsWithChildren) ...getBranchLikeQuery(branchLike), })} value={newSecurityHotspots} - failedConditions={failedConditions} - failingConditionMetric={MetricKey.new_security_hotspots_reviewed} - requireLabel={intl.formatMessage( - { id: 'overview.quality_gate.require_reviewing' }, - { - count: newSecurityHotspots, - }, - )} + conditions={conditions} + conditionMetric={MetricKey.new_security_hotspots_reviewed} /> ) branchLike, listView: true, })} - failedConditions={failedConditions} - failingConditionMetric={MetricKey.new_duplicated_lines_density} + conditions={conditions} + conditionMetric={MetricKey.new_duplicated_lines_density} newLinesMetric={MetricKey.new_lines} afterMergeMetric={MetricKey.duplicated_lines_density} measures={measures} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresCardPercent.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCardPercent.tsx similarity index 81% rename from server/sonar-web/src/main/js/apps/overview/branches/MeasuresCardPercent.tsx rename to server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCardPercent.tsx index fed7c608dc3..a7a9a16172a 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresCardPercent.tsx +++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCardPercent.tsx @@ -35,7 +35,7 @@ import { BranchLike } from '../../../types/branch-like'; import { MetricKey, MetricType } from '../../../types/metrics'; import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates'; import { MeasureEnhanced } from '../../../types/types'; -import { MeasurementType, getMeasurementMetricKey } from '../utils'; +import { MeasurementType, Status, getMeasurementMetricKey } from '../utils'; import MeasuresCard from './MeasuresCard'; interface Props { @@ -45,8 +45,8 @@ interface Props { label: string; url: To; measures: MeasureEnhanced[]; - failedConditions: QualityGateStatusConditionEnhanced[]; - failingConditionMetric: MetricKey; + conditions: QualityGateStatusConditionEnhanced[]; + conditionMetric: MetricKey; newLinesMetric: MetricKey; afterMergeMetric: MetricKey; } @@ -61,8 +61,8 @@ export default function MeasuresCardPercent( label, url, measures, - failedConditions, - failingConditionMetric, + conditions, + conditionMetric, newLinesMetric, afterMergeMetric, } = props; @@ -87,27 +87,21 @@ export default function MeasuresCardPercent( const afterMergeValue = findMeasure(measures, afterMergeMetric)?.value; - const failedCondition = failedConditions.find( - (condition) => condition.metric === failingConditionMetric, - ); + const condition = conditions.find((c) => c.metric === conditionMetric); + const conditionFailed = condition?.level === Status.ERROR; - let errorRequireLabel = ''; - if (failedCondition) { - errorRequireLabel = intl.formatMessage( + const requireLabel = + condition && + intl.formatMessage( { id: 'overview.quality_gate.required_x' }, { - operator: failedCondition.op === 'GT' ? '≤' : '≥', - value: formatMeasure( - failedCondition.level === 'ERROR' ? failedCondition.error : failedCondition.warning, - MetricType.Percent, - { - decimals: 2, - omitExtraDecimalZeros: true, - }, - ), + operator: condition.op === 'GT' ? '≤' : '≥', + value: formatMeasure(condition.error, MetricType.Percent, { + decimals: 2, + omitExtraDecimalZeros: true, + }), }, ); - } return ( -
- + <> + {requireLabel && + (conditionFailed ? ( + + ) : ( + {requireLabel} + ))} + + )} - - {failedCondition && ( - - )} -
+
); } diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx index 5c0d965b20c..40cc145f709 100644 --- a/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx +++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx @@ -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 { BasicSeparator, CenteredLayout, Spinner } from 'design-system'; +import { BasicSeparator, CenteredLayout, PageContentFontWrapper, Spinner } from 'design-system'; import { uniq } from 'lodash'; import * as React from 'react'; import { useEffect, useState } from 'react'; @@ -29,13 +29,13 @@ import { isDefined } from '../../../helpers/types'; import { useBranchStatusQuery } from '../../../queries/branch'; import { PullRequest } from '../../../types/branch-like'; import { Component, MeasureEnhanced, QualityGate } from '../../../types/types'; -import MeasuresCardPanel from '../branches/MeasuresCardPanel'; import BranchQualityGate from '../components/BranchQualityGate'; import IgnoredConditionWarning from '../components/IgnoredConditionWarning'; import MetaTopBar from '../components/MetaTopBar'; import ZeroNewIssuesSimplificationGuide from '../components/ZeroNewIssuesSimplificationGuide'; import '../styles.css'; import { PR_METRICS, Status } from '../utils'; +import MeasuresCardPanel from './MeasuresCardPanel'; import SonarLintAd from './SonarLintAd'; interface Props { @@ -60,11 +60,8 @@ export default function PullRequestOverview(props: Props) { const metricKeys = conditions !== undefined - ? // Also load metrics that apply to failing QG conditions. - uniq([ - ...PR_METRICS, - ...conditions.filter((c) => c.level !== Status.OK).map((c) => c.metric), - ]) + ? // Also load metrics that apply to QG conditions. + uniq([...PR_METRICS, ...conditions.map((c) => c.metric)]) : PR_METRICS; getMeasuresWithMetrics(component.key, metricKeys, getBranchLikeQuery(branchLike)).then( @@ -108,14 +105,17 @@ export default function PullRequestOverview(props: Props) { return null; } - const failedConditions = conditions - .filter((condition) => condition.level === Status.ERROR) + const enhancedConditions = conditions .map((c) => enhanceConditionWithMeasure(c, measures)) .filter(isDefined); + const failedConditions = enhancedConditions.filter( + (condition) => condition.level === Status.ERROR, + ); + return ( -
+
@@ -135,7 +135,7 @@ export default function PullRequestOverview(props: Props) { className="sw-flex-1" branchLike={branchLike} component={component} - failedConditions={failedConditions} + conditions={enhancedConditions} measures={measures} /> @@ -143,7 +143,7 @@ export default function PullRequestOverview(props: Props) {
-
+
); } diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/PullRequestOverview-it.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/PullRequestOverview-it.tsx index 3a80968c392..2755d3b3abf 100644 --- a/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/PullRequestOverview-it.tsx +++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/PullRequestOverview-it.tsx @@ -180,12 +180,12 @@ it('should render correctly for a failed QG', async () => { expect( byRole('link', { - name: 'overview.failed_condition.x_required 10.0% duplicated_lines ≤ 1.0%', + name: 'overview.measures.failed_badge overview.failed_condition.x_required 10.0% duplicated_lines ≤ 1.0%', }).get(), ).toBeInTheDocument(); expect( byRole('link', { - name: 'overview.failed_condition.x_required 10 new_bugs ≤ 3', + name: 'overview.measures.failed_badge overview.failed_condition.x_required 10 new_bugs ≤ 3', }).get(), ).toBeInTheDocument(); }); diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index a6c112d939b..d7947d0221d 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -3802,8 +3802,8 @@ system.version_is_availble={version} is available #------------------------------------------------------------------------------ overview.1_condition_failed=1 failed condition overview.X_conditions_failed={0} failed conditions -overview.failed_condition.x_rating_required={rating} is {value} required {threshold} -overview.failed_condition.x_required={metric} required {threshold} +overview.failed_condition.x_rating_required={rating} is {value}. Required {threshold} +overview.failed_condition.x_required={metric}. Required {threshold} overview.fix_failed_conditions_with_sonarlint=Fix issues before they fail your Quality Gate with {link} in your IDE. Power up with connected mode! overview.quality_gate.status=Quality Gate Status overview.quality_gate=Quality Gate @@ -3835,7 +3835,7 @@ overview.quality_gate.on_x_new_lines=On {link} New Lines overview.quality_gate.x_estimated_after_merge={value} Estimated after merge overview.quality_gate.require_fixing={count, plural, one {requires} other {require}} fixing overview.quality_gate.require_reviewing={count, plural, one {requires} other {require}} reviewing -overview.quality_gate.required_x=required {operator} {value} +overview.quality_gate.required_x=Required {operator} {value} overview.quality_profiles=Quality Profiles used overview.new_code_period_x=New Code: {0} overview.max_new_code_period_from_x=Max New Code from: {0} @@ -3920,6 +3920,8 @@ overview.gate.view.errors=The view failed the quality gate on the following cond overview.measurement_type.DUPLICATION=Duplications overview.measurement_type.COVERAGE=Coverage +overview.measures.failed_badge=Failed + overview.complexity_tooltip.function={0} functions have complexity around {1} overview.complexity_tooltip.file={0} files have complexity around {1} -- 2.39.5