diff options
author | Jay <jeremy.davis@sonarsource.com> | 2023-12-08 17:09:43 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-12-08 20:03:05 +0000 |
commit | 99f771c79563dcb26fbf71cee8a6da2deab7ee8f (patch) | |
tree | e26ce017553fd4b9a8639a02a876e9fbf59c17a1 /server | |
parent | f2624c272d3bcea46540937ea6bddb7d691683f0 (diff) | |
download | sonarqube-99f771c79563dcb26fbf71cee8a6da2deab7ee8f.tar.gz sonarqube-99f771c79563dcb26fbf71cee8a6da2deab7ee8f.zip |
SONAR-21215 Update PR Overview UI
Diffstat (limited to 'server')
8 files changed, 113 insertions, 117 deletions
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 ( - <DangerButtonSecondary className="sw-px-2 sw-py-1 sw-rounded-1/2 sw-body-sm" to={url}> - <FailedMetric condition={condition} /> - <ChevronRightIcon className="sw-ml-1" /> - </DangerButtonSecondary> + <ButtonSecondary className="sw-px-2 sw-py-1 sw-rounded-1/2 sw-body-sm" to={url}> + <Badge className="sw-mr-2" variant="deleted"> + {translate('overview.measures.failed_badge')} + </Badge> + <SpanDanger> + <FailedMetric condition={condition} /> + </SpanDanger> + </ButtonSecondary> ); } @@ -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 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 ( - <StyledCard - className={classNames( - 'sw-h-fit sw-p-8 sw-rounded-2 sw-flex sw-justify-between sw-items-center sw-text-base', - { - failed, - }, - )} + <Card + className="sw-h-fit sw-p-8 sw-rounded-2 sw-flex sw-justify-between sw-items-center sw-text-base" {...rest} > - <PageContentFontWrapper className="sw-flex sw-flex-col sw-gap-1 sw-justify-between"> + <div className="sw-flex sw-flex-col sw-gap-1 sw-justify-between"> <div className="sw-flex sw-items-center sw-gap-2 sw-font-semibold"> {value ? ( <ContentLink @@ -68,21 +62,20 @@ export default function MeasuresCard( <StyledNoValue> — </StyledNoValue> )} {translate(label)} + {failed && ( + <Badge className="sw-mt-1/2" variant="deleted"> + {translate('overview.measures.failed_badge')} + </Badge> + )} </div> {children && <div className="sw-flex sw-flex-col">{children}</div>} - </PageContentFontWrapper> + </div> {icon && <div>{icon}</div>} - </StyledCard> + </Card> ); } 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 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<Props & React.HTMLAttributes<HTMLDivElement>>, ) { - 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 ( <MeasuresCard url={url} value={formatMeasure(value, MetricType.ShortInteger)} - metric={failingConditionMetric} + metric={conditionMetric} label={label} - failed={failed} - data-guiding-id={failed ? guidingKeyOnError : undefined} + failed={conditionFailed} + data-guiding-id={conditionFailed ? guidingKeyOnError : undefined} {...rest} > - {failed && <TextError className="sw-font-regular sw-mt-2" text={requireLabel} />} + {requireLabel && + (conditionFailed ? ( + <TextError className="sw-mt-2 sw-font-regular" text={requireLabel} /> + ) : ( + <LightLabel className="sw-mt-2">{requireLabel}</LightLabel> + ))} </MeasuresCard> ); } 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 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<Props>) { - 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<Props>) ...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<Props>) 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<Props>) ...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} /> <MeasuresCardPercent @@ -128,8 +113,8 @@ export default function MeasuresCardPanel(props: React.PropsWithChildren<Props>) 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 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 ( <MeasuresCard @@ -115,11 +109,18 @@ export default function MeasuresCardPercent( metric={metricKey} url={url} label={label} - failed={Boolean(failedCondition)} + failed={conditionFailed} icon={renderIcon(measurementType, value)} > - <div className="sw-flex sw-flex-col"> - <LightLabel className="sw-flex sw-items-center sw-gap-1"> + <> + {requireLabel && + (conditionFailed ? ( + <TextError className="sw-mt-2 sw-font-regular" text={requireLabel} /> + ) : ( + <LightLabel className="sw-mt-2">{requireLabel}</LightLabel> + ))} + + <LightLabel className="sw-flex sw-items-center sw-gap-1 sw-mt-4"> <FormattedMessage defaultMessage={translate(newLinesLabel)} id={newLinesLabel} @@ -152,11 +153,7 @@ export default function MeasuresCardPercent( /> </LightLabel> )} - - {failedCondition && ( - <TextError className="sw-mt-2 sw-font-regular" text={errorRequireLabel} /> - )} - </div> + </> </MeasuresCard> ); } 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 ( <CenteredLayout> - <div className="it__pr-overview sw-mt-12 sw-mb-8 sw-grid sw-grid-cols-12"> + <PageContentFontWrapper className="it__pr-overview sw-mt-12 sw-mb-8 sw-grid sw-grid-cols-12 sw-body-sm"> <div className="sw-col-start-2 sw-col-span-10"> <MetaTopBar branchLike={branchLike} measures={measures} /> <BasicSeparator className="sw-my-4" /> @@ -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) { <SonarLintAd status={status} /> </div> - </div> + </PageContentFontWrapper> </CenteredLayout> ); } 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(); }); |