diff options
author | Revanshu Paliwal <revanshu.paliwal@sonarsource.com> | 2024-06-13 14:13:40 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-06-18 20:02:41 +0000 |
commit | 5ff3751aa69b21ac0269eb9bc1b54625d42ecc08 (patch) | |
tree | a4e9839ccc8f091d7ab721312598acb6c19f365b /server | |
parent | 0a404611b8d3db1c45c0fd64927ca8d0c42458e2 (diff) | |
download | sonarqube-5ff3751aa69b21ac0269eb9bc1b54625d42ecc08.tar.gz sonarqube-5ff3751aa69b21ac0269eb9bc1b54625d42ecc08.zip |
SONAR-22385 Updating layout for new and overall code inside project overview
Diffstat (limited to 'server')
14 files changed, 565 insertions, 402 deletions
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx index cce94e2a327..3307b95ed06 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx @@ -135,7 +135,6 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp <AnalysisMissingInfoMessage qualifier={component.qualifier} hide={isPortfolioLike(component.qualifier)} - className="sw-mt-6" /> ); @@ -222,7 +221,10 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp </> )} <AnalysisStatus className="sw-mt-6" component={component} /> - <div className="sw-flex sw-justify-between sw-items-start sw-my-6"> + <div + data-testid="overview__quality-gate-panel" + className="sw-flex sw-justify-between sw-items-start sw-my-6" + > <QGStatus status={qgStatus} titleSize="extra-large" /> <LastAnalysisLabel analysisDate={branch?.analysisDate} /> </div> @@ -243,6 +245,10 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp branch={branch} component={component} measures={measures} + appLeak={appLeak} + period={period} + loading={loadingStatus} + qualityGate={qualityGate} /> ) : ( <MeasuresPanelNoNewCode @@ -262,6 +268,8 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp qgStatuses={qgStatuses} component={component} measures={measures} + loading={loadingStatus} + qualityGate={qualityGate} /> </> )} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/BranchSummaryStyles.tsx b/server/sonar-web/src/main/js/apps/overview/branches/BranchSummaryStyles.tsx new file mode 100644 index 00000000000..82cfda5f355 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/BranchSummaryStyles.tsx @@ -0,0 +1,69 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import styled from '@emotion/styled'; +import { themeColor } from 'design-system/lib'; + +export const GridContainer = styled.div` + --grids-gaps: var(--echoes-dimension-space-500); + display: grid; + grid-template-columns: repeat(12, minmax(0, 1fr)); + gap: var(--grids-gaps); +`; + +export const StyleMeasuresCard = styled.div` + box-sizing: border-box; + position: relative; + + &:not(:last-child):before { + content: ''; + position: absolute; + top: 0; + right: calc(var(--grids-gaps) / -2); + height: 100%; + width: 1px; + background: ${themeColor('pageBlockBorder')}; + } + + &:not(:last-child):after { + content: ''; + position: absolute; + bottom: calc(var(--grids-gaps) / -2); + right: 0; + left: 0px; + height: 1px; + width: 100vw; + background: ${themeColor('pageBlockBorder')}; + } +`; + +export const StyledConditionsCard = styled.div` + box-sizing: border-box; + position: relative; + &:before { + content: ''; + position: absolute; + top: 0; + right: calc(var(--grids-gaps) / -2); + height: 100%; + width: 1px; + background: ${themeColor('pageBlockBorder')}; + } +`; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/FailedConditions.tsx b/server/sonar-web/src/main/js/apps/overview/branches/FailedConditions.tsx new file mode 100644 index 00000000000..d81e6250b12 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/FailedConditions.tsx @@ -0,0 +1,80 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { CardSeparator, TextError } from 'design-system'; +import _ from 'lodash'; +import * as React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { isDiffMetric } from '../../../helpers/measures'; +import { QualityGateStatus } from '../../../types/quality-gates'; +import { QualityGate } from '../../../types/types'; +import ZeroNewIssuesSimplificationGuide from '../components/ZeroNewIssuesSimplificationGuide'; +import QualityGateConditions from './QualityGateConditions'; + +export interface FailedConditionsProps { + isApplication?: boolean; + isNewCode: boolean; + qgStatus: QualityGateStatus; + qualityGate?: QualityGate; +} + +export default function FailedConditions({ + isApplication, + isNewCode, + qualityGate, + qgStatus, +}: FailedConditionsProps) { + const { failedConditions, branchLike } = qgStatus; + const [newCodeFailedConditions, overallFailedConditions] = _.partition( + failedConditions, + (condition) => isDiffMetric(condition.metric), + ); + + return ( + <> + {!isApplication && ( + <> + <TextError + className="sw-mb-3" + text={ + <FormattedMessage + id="quality_gates.conditions.x_conditions_failed" + values={{ + conditions: isNewCode + ? newCodeFailedConditions.length + : overallFailedConditions.length, + }} + /> + } + /> + <CardSeparator /> + </> + )} + {qualityGate?.isBuiltIn && isNewCode && ( + <ZeroNewIssuesSimplificationGuide qualityGate={qualityGate} /> + )} + <QualityGateConditions + component={qgStatus} + branchLike={branchLike} + failedConditions={isNewCode ? newCodeFailedConditions : overallFailedConditions} + isBuiltInQualityGate={isNewCode && qualityGate?.isBuiltIn} + /> + </> + ); +} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelPercentCards.tsx b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelPercentCards.tsx deleted file mode 100644 index faeda3e4f70..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelPercentCards.tsx +++ /dev/null @@ -1,91 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { MetricKey } from '~sonar-aligned/types/metrics'; -import { getComponentDrilldownUrl } from '../../../helpers/urls'; -import { BranchLike } from '../../../types/branch-like'; -import { isApplication } from '../../../types/component'; -import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates'; -import { Component, MeasureEnhanced } from '../../../types/types'; -import MeasuresCardPercent from '../components/MeasuresCardPercent'; -import { MeasurementType, getMeasurementMetricKey } from '../utils'; - -interface Props { - branch?: BranchLike; - component: Component; - conditions: QualityGateStatusConditionEnhanced[]; - measures: MeasureEnhanced[]; - useDiffMetric?: boolean; -} - -/** - * Renders Coverage and Duplication cards for the Overview page. - */ -export default function MeasuresPanelPercentCards(props: Readonly<Props>) { - const { useDiffMetric, branch, component, measures, conditions } = props; - - const isApp = isApplication(component.qualifier); - - return ( - <> - <MeasuresCardPercent - branchLike={branch} - componentKey={component.key} - conditions={conditions} - measures={measures} - measurementType={MeasurementType.Coverage} - label="overview.quality_gate.coverage" - url={getComponentDrilldownUrl({ - componentKey: component.key, - metric: getMeasurementMetricKey(MeasurementType.Coverage, Boolean(useDiffMetric)), - branchLike: branch, - listView: true, - })} - conditionMetric={useDiffMetric ? MetricKey.new_coverage : MetricKey.coverage} - linesMetric={useDiffMetric ? MetricKey.new_lines_to_cover : MetricKey.lines_to_cover} - useDiffMetric={useDiffMetric} - showRequired={!isApp} - /> - - <MeasuresCardPercent - branchLike={branch} - componentKey={component.key} - conditions={conditions} - measures={measures} - measurementType={MeasurementType.Duplication} - label="overview.quality_gate.duplications" - url={getComponentDrilldownUrl({ - componentKey: component.key, - metric: getMeasurementMetricKey(MeasurementType.Duplication, Boolean(useDiffMetric)), - branchLike: branch, - listView: true, - })} - conditionMetric={ - useDiffMetric - ? MetricKey.new_duplicated_lines_density - : MetricKey.duplicated_lines_density - } - linesMetric={useDiffMetric ? MetricKey.new_lines : MetricKey.lines} - useDiffMetric={useDiffMetric} - showRequired={!isApp} - /> - </> - ); -} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx index 9d86007dc2f..6c061fd454b 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx @@ -18,8 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import styled from '@emotion/styled'; +import classNames from 'classnames'; import { - LightGreyCard, LightLabel, MetricsRatingBadge, NoDataIcon, @@ -41,32 +41,48 @@ import { import { MetricKey, MetricType } from '~sonar-aligned/types/metrics'; import { getLeakValue } from '../../../components/measure/utils'; import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils'; -import { findMeasure, formatRating } from '../../../helpers/measures'; -import { CodeScope } from '../../../helpers/urls'; +import { translate } from '../../../helpers/l10n'; +import { findMeasure, formatRating, isDiffMetric } from '../../../helpers/measures'; +import { CodeScope, getComponentDrilldownUrl } from '../../../helpers/urls'; +import { ApplicationPeriod } from '../../../types/application'; import { Branch } from '../../../types/branch-like'; import { isApplication } from '../../../types/component'; import { IssueStatus } from '../../../types/issues'; import { QualityGateStatus } from '../../../types/quality-gates'; -import { Component, MeasureEnhanced } from '../../../types/types'; +import { CaycStatus, Component, MeasureEnhanced, Period, QualityGate } from '../../../types/types'; import { IssueMeasuresCardInner } from '../components/IssueMeasuresCardInner'; import MeasuresCardNumber from '../components/MeasuresCardNumber'; -import { Status, getConditionRequiredLabel } from '../utils'; -import MeasuresPanelPercentCards from './MeasuresPanelPercentCards'; +import MeasuresCardPercent from '../components/MeasuresCardPercent'; +import { + MeasurementType, + Status, + getConditionRequiredLabel, + getMeasurementMetricKey, +} from '../utils'; +import { GridContainer, StyleMeasuresCard, StyledConditionsCard } from './BranchSummaryStyles'; +import { LeakPeriodInfo } from './LeakPeriodInfo'; +import QualityGatePanel from './QualityGatePanel'; interface Props { + appLeak?: ApplicationPeriod; branch?: Branch; component: Component; + loading?: boolean; measures: MeasureEnhanced[]; + period?: Period; qgStatuses?: QualityGateStatus[]; + qualityGate?: QualityGate; } export default function NewCodeMeasuresPanel(props: Readonly<Props>) { - const { branch, component, measures, qgStatuses } = props; + const { appLeak, branch, component, measures, qgStatuses, period, loading, qualityGate } = props; const intl = useIntl(); const isApp = isApplication(component.qualifier); const conditions = qgStatuses?.flatMap((qg) => qg.conditions) ?? []; + const totalFailedCondition = qgStatuses?.flatMap((qg) => qg.failedConditions) ?? []; + const totalNewFailedCondition = totalFailedCondition.filter((c) => isDiffMetric(c.metric)); const newIssues = getLeakValue(findMeasure(measures, MetricKey.new_violations)); const newIssuesCondition = conditions.find((c) => c.metric === MetricKey.new_violations); const issuesConditionFailed = newIssuesCondition?.level === Status.ERROR; @@ -111,96 +127,186 @@ export default function NewCodeMeasuresPanel(props: Readonly<Props>) { ); } - return ( - <div className="sw-grid sw-grid-cols-2 sw-gap-4 sw-mt-6" id={getTabPanelId(CodeScope.New)}> - <LightGreyCard className="sw-flex sw-col-span-2 sw-rounded-2 sw-gap-4"> - <IssueMeasuresCardInner - data-testid="overview__measures-new_issues" - disabled={component.needIssueSync} - className="sw-w-1/2" - metric={MetricKey.new_violations} - value={formatMeasure(newIssues, MetricType.ShortInteger)} - header={intl.formatMessage({ - id: 'overview.new_issues', - })} - url={getComponentIssuesUrl(component.key, { - ...getBranchLikeQuery(branch), - ...DEFAULT_ISSUES_QUERY, - inNewCodePeriod: 'true', - })} - failed={issuesConditionFailed} - icon={issuesConditionFailed && <TrendUpCircleIcon />} - footer={issuesFooter} - /> - <StyledCardSeparator /> - <IssueMeasuresCardInner - data-testid="overview__measures-accepted_issues" - disabled={Boolean(component.needIssueSync) || !newAcceptedIssues} - className="sw-w-1/2" - metric={MetricKey.new_accepted_issues} - value={formatMeasure(newAcceptedIssues, MetricType.ShortInteger)} - header={intl.formatMessage({ - id: 'overview.accepted_issues', - })} - url={getComponentIssuesUrl(component.key, { - ...getBranchLikeQuery(branch), - issueStatuses: IssueStatus.Accepted, - inNewCodePeriod: 'true', - })} - footer={acceptedIssuesFooter} - icon={ - <SnoozeCircleIcon - color={ - newAcceptedIssues === '0' ? 'overviewCardDefaultIcon' : 'overviewCardWarningIcon' - } - /> - } - /> - </LightGreyCard> + const leakPeriod = isApp ? appLeak : period; - <MeasuresPanelPercentCards - useDiffMetric - branch={branch} - component={component} - measures={measures} - conditions={conditions} - /> + const nonCaycProjectsInApp = + isApp && qgStatuses + ? qgStatuses + .filter(({ caycStatus }) => caycStatus === CaycStatus.NonCompliant) + .sort(({ name: a }, { name: b }) => + a.localeCompare(b, undefined, { sensitivity: 'base' }), + ) + : []; + + const showCaycWarningInProject = + qgStatuses && + qgStatuses.length === 1 && + qgStatuses[0].caycStatus === CaycStatus.NonCompliant && + qualityGate?.actions?.manageConditions && + !isApp; + + const showCaycWarningInApp = nonCaycProjectsInApp.length > 0; - <MeasuresCardNumber - label={ - newSecurityHotspots === '1' - ? 'issue.type.SECURITY_HOTSPOT' - : 'issue.type.SECURITY_HOTSPOT.plural' - } - url={getComponentSecurityHotspotsUrl(component.key, branch, { - inNewCodePeriod: 'true', - })} - value={newSecurityHotspots} - metric={MetricKey.new_security_hotspots} - conditions={conditions} - conditionMetric={MetricKey.new_security_hotspots_reviewed} - showRequired={!isApp} - icon={ - newSecurityReviewRating ? ( - <MetricsRatingBadge - label={newSecurityReviewRating} - rating={formatRating(newSecurityReviewRating)} - size="md" + const noConditionsAndWarningForNewCode = + totalNewFailedCondition.length === 0 && !showCaycWarningInApp && !showCaycWarningInProject; + + const isTwoColumns = !noConditionsAndWarningForNewCode; + const isThreeColumns = noConditionsAndWarningForNewCode; + + return ( + <div id={getTabPanelId(CodeScope.New)}> + {leakPeriod && ( + <span + className="sw-body-xs sw-flex sw-items-center sw-mt-8 sw-mr-6" + data-spotlight-id="cayc-promotion-2" + > + <LightLabel className="sw-mr-1">{translate('overview.new_code')}:</LightLabel> + <b className="sw-flex"> + <LeakPeriodInfo leakPeriod={leakPeriod} /> + </b> + </span> + )} + <GridContainer className=" sw-relative sw-overflow-hidden sw-mt-8 js-summary"> + {!noConditionsAndWarningForNewCode && ( + <StyledConditionsCard className="sw-row-span-4 sw-col-span-4"> + <QualityGatePanel + component={component} + loading={loading} + qgStatuses={qgStatuses} + qualityGate={qualityGate} + isNewCode + showCaycWarningInApp={showCaycWarningInApp} + showCaycWarningInProject={showCaycWarningInProject ?? false} + totalFailedConditionLength={totalNewFailedCondition.length} /> - ) : ( - <NoDataIcon size="md" /> - ) - } - /> + </StyledConditionsCard> + )} + <StyleMeasuresCard + className={classNames({ + 'sw-col-span-4': isTwoColumns, + 'sw-col-span-6': isThreeColumns, + })} + > + <IssueMeasuresCardInner + data-testid="overview__measures-new_issues" + disabled={component.needIssueSync} + metric={MetricKey.new_violations} + value={formatMeasure(newIssues, MetricType.ShortInteger)} + header={intl.formatMessage({ + id: 'overview.new_issues', + })} + url={getComponentIssuesUrl(component.key, { + ...getBranchLikeQuery(branch), + ...DEFAULT_ISSUES_QUERY, + inNewCodePeriod: 'true', + })} + failed={issuesConditionFailed} + icon={issuesConditionFailed && <TrendUpCircleIcon />} + footer={issuesFooter} + /> + </StyleMeasuresCard> + <StyleMeasuresCard + className={classNames({ + 'sw-col-span-4': isTwoColumns, + 'sw-col-span-6': isThreeColumns, + })} + > + <IssueMeasuresCardInner + data-testid="overview__measures-accepted_issues" + disabled={Boolean(component.needIssueSync) || !newAcceptedIssues} + metric={MetricKey.new_accepted_issues} + value={formatMeasure(newAcceptedIssues, MetricType.ShortInteger)} + header={intl.formatMessage({ + id: 'overview.accepted_issues', + })} + url={getComponentIssuesUrl(component.key, { + ...getBranchLikeQuery(branch), + issueStatuses: IssueStatus.Accepted, + inNewCodePeriod: 'true', + })} + footer={acceptedIssuesFooter} + icon={ + <SnoozeCircleIcon + color={ + newAcceptedIssues === '0' ? 'overviewCardDefaultIcon' : 'overviewCardWarningIcon' + } + /> + } + /> + </StyleMeasuresCard> + <StyleMeasuresCard className="sw-col-span-4"> + <MeasuresCardPercent + branchLike={branch} + componentKey={component.key} + conditions={conditions} + measures={measures} + measurementType={MeasurementType.Coverage} + label="overview.quality_gate.coverage" + url={getComponentDrilldownUrl({ + componentKey: component.key, + metric: getMeasurementMetricKey(MeasurementType.Coverage, true), + branchLike: branch, + listView: true, + })} + conditionMetric={MetricKey.new_coverage} + linesMetric={MetricKey.new_lines_to_cover} + useDiffMetric + showRequired={!isApp} + /> + </StyleMeasuresCard> + <StyleMeasuresCard className="sw-col-span-4"> + <MeasuresCardPercent + branchLike={branch} + componentKey={component.key} + conditions={conditions} + measures={measures} + measurementType={MeasurementType.Duplication} + label="overview.quality_gate.duplications" + url={getComponentDrilldownUrl({ + componentKey: component.key, + metric: getMeasurementMetricKey(MeasurementType.Duplication, true), + branchLike: branch, + listView: true, + })} + conditionMetric={MetricKey.new_duplicated_lines_density} + linesMetric={MetricKey.new_lines} + useDiffMetric + showRequired={!isApp} + /> + </StyleMeasuresCard> + <StyleMeasuresCard className="sw-col-span-4"> + <MeasuresCardNumber + label={ + newSecurityHotspots === '1' + ? 'issue.type.SECURITY_HOTSPOT' + : 'issue.type.SECURITY_HOTSPOT.plural' + } + url={getComponentSecurityHotspotsUrl(component.key, branch, { + inNewCodePeriod: 'true', + })} + value={newSecurityHotspots} + metric={MetricKey.new_security_hotspots} + conditions={conditions} + conditionMetric={MetricKey.new_security_hotspots_reviewed} + showRequired={!isApp} + icon={ + newSecurityReviewRating ? ( + <MetricsRatingBadge + label={newSecurityReviewRating} + rating={formatRating(newSecurityReviewRating)} + size="md" + /> + ) : ( + <NoDataIcon size="md" /> + ) + } + /> + </StyleMeasuresCard> + </GridContainer> </div> ); } -const StyledCardSeparator = styled.div` - width: 1px; - background-color: ${themeColor('projectCardBorder')}; -`; - const StyledInfoMessage = styled.div` background-color: ${themeColor('projectCardInfo')}; `; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx index 9bd22123d35..263cf0d6028 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx @@ -17,6 +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 classNames from 'classnames'; import { MetricsRatingBadge, NoDataIcon, @@ -33,40 +34,87 @@ import { getComponentSecurityHotspotsUrl, } from '~sonar-aligned/helpers/urls'; import { MetricKey, MetricType } from '~sonar-aligned/types/metrics'; -import { findMeasure, formatRating } from '../../../helpers/measures'; -import { CodeScope } from '../../../helpers/urls'; +import { findMeasure, formatRating, isDiffMetric } from '../../../helpers/measures'; +import { CodeScope, getComponentDrilldownUrl } from '../../../helpers/urls'; import { Branch } from '../../../types/branch-like'; import { SoftwareQuality } from '../../../types/clean-code-taxonomy'; import { isApplication } from '../../../types/component'; import { IssueStatus } from '../../../types/issues'; import { QualityGateStatus } from '../../../types/quality-gates'; -import { Component, MeasureEnhanced } from '../../../types/types'; +import { CaycStatus, Component, MeasureEnhanced, QualityGate } from '../../../types/types'; import MeasuresCard from '../components/MeasuresCard'; import MeasuresCardNumber from '../components/MeasuresCardNumber'; -import MeasuresPanelPercentCards from './MeasuresPanelPercentCards'; +import MeasuresCardPercent from '../components/MeasuresCardPercent'; +import { MeasurementType, getMeasurementMetricKey } from '../utils'; +import { GridContainer, StyleMeasuresCard, StyledConditionsCard } from './BranchSummaryStyles'; +import QualityGatePanel from './QualityGatePanel'; import SoftwareImpactMeasureCard from './SoftwareImpactMeasureCard'; export interface OverallCodeMeasuresPanelProps { branch?: Branch; component: Component; + loading?: boolean; measures: MeasureEnhanced[]; qgStatuses?: QualityGateStatus[]; + qualityGate?: QualityGate; } export default function OverallCodeMeasuresPanel(props: Readonly<OverallCodeMeasuresPanelProps>) { - const { branch, qgStatuses, component, measures } = props; + const { branch, qgStatuses, component, measures, loading, qualityGate } = props; const intl = useIntl(); const isApp = isApplication(component.qualifier); const conditions = qgStatuses?.flatMap((qg) => qg.conditions) ?? []; + const totalFailedCondition = qgStatuses?.flatMap((qg) => qg.failedConditions) ?? []; + const totalOverallFailedCondition = totalFailedCondition.filter((c) => !isDiffMetric(c.metric)); const acceptedIssues = findMeasure(measures, MetricKey.accepted_issues)?.value; const securityHotspots = findMeasure(measures, MetricKey.security_hotspots)?.value; const securityRating = findMeasure(measures, MetricKey.security_review_rating)?.value; + const nonCaycProjectsInApp = + isApp && qgStatuses + ? qgStatuses + .filter(({ caycStatus }) => caycStatus === CaycStatus.NonCompliant) + .sort(({ name: a }, { name: b }) => + a.localeCompare(b, undefined, { sensitivity: 'base' }), + ) + : []; + + const showCaycWarningInProject = + qgStatuses && + qgStatuses.length === 1 && + qgStatuses[0].caycStatus === CaycStatus.NonCompliant && + qualityGate?.actions?.manageConditions && + !isApp; + + const showCaycWarningInApp = nonCaycProjectsInApp.length > 0; + + const noConditionsAndWarningForOverallCode = + totalOverallFailedCondition.length === 0 && !showCaycWarningInApp && !showCaycWarningInProject; + return ( - <div id={getTabPanelId(CodeScope.Overall)} className="sw-mt-6"> - <div className="sw-flex sw-gap-4"> + <GridContainer + id={getTabPanelId(CodeScope.Overall)} + className={classNames('sw-grid sw-gap-12 sw-relative sw-overflow-hidden sw-mt-8 js-summary', { + 'sw-grid-cols-3': noConditionsAndWarningForOverallCode, + 'sw-grid-cols-4': !noConditionsAndWarningForOverallCode, + })} + > + {!noConditionsAndWarningForOverallCode && ( + <StyledConditionsCard className="sw-row-span-4"> + <QualityGatePanel + component={component} + loading={loading} + qgStatuses={qgStatuses} + qualityGate={qualityGate} + showCaycWarningInApp={showCaycWarningInApp} + showCaycWarningInProject={showCaycWarningInProject ?? false} + totalFailedConditionLength={totalOverallFailedCondition.length} + /> + </StyledConditionsCard> + )} + <StyleMeasuresCard> <SoftwareImpactMeasureCard branch={branch} component={component} @@ -75,6 +123,8 @@ export default function OverallCodeMeasuresPanel(props: Readonly<OverallCodeMeas ratingMetricKey={MetricKey.security_rating} measures={measures} /> + </StyleMeasuresCard> + <StyleMeasuresCard> <SoftwareImpactMeasureCard branch={branch} component={component} @@ -83,6 +133,8 @@ export default function OverallCodeMeasuresPanel(props: Readonly<OverallCodeMeas ratingMetricKey={MetricKey.reliability_rating} measures={measures} /> + </StyleMeasuresCard> + <StyleMeasuresCard> <SoftwareImpactMeasureCard branch={branch} component={component} @@ -91,9 +143,8 @@ export default function OverallCodeMeasuresPanel(props: Readonly<OverallCodeMeas ratingMetricKey={MetricKey.sqale_rating} measures={measures} /> - </div> - - <div className="sw-grid sw-grid-cols-2 sw-gap-4 sw-mt-4"> + </StyleMeasuresCard> + <StyleMeasuresCard> <MeasuresCard url={getComponentIssuesUrl(component.key, { ...getBranchLikeQuery(branch), @@ -115,14 +166,46 @@ export default function OverallCodeMeasuresPanel(props: Readonly<OverallCodeMeas })} </TextSubdued> </MeasuresCard> - - <MeasuresPanelPercentCards - branch={branch} - component={component} + </StyleMeasuresCard> + <StyleMeasuresCard> + <MeasuresCardPercent + branchLike={branch} + componentKey={component.key} + conditions={conditions} measures={measures} + measurementType={MeasurementType.Coverage} + label="overview.quality_gate.coverage" + url={getComponentDrilldownUrl({ + componentKey: component.key, + metric: getMeasurementMetricKey(MeasurementType.Coverage, false), + branchLike: branch, + listView: true, + })} + conditionMetric={MetricKey.coverage} + linesMetric={MetricKey.lines_to_cover} + showRequired={!isApp} + /> + </StyleMeasuresCard> + <StyleMeasuresCard> + <MeasuresCardPercent + branchLike={branch} + componentKey={component.key} conditions={conditions} + measures={measures} + measurementType={MeasurementType.Duplication} + label="overview.quality_gate.duplications" + url={getComponentDrilldownUrl({ + componentKey: component.key, + metric: getMeasurementMetricKey(MeasurementType.Duplication, false), + branchLike: branch, + listView: true, + })} + conditionMetric={MetricKey.duplicated_lines_density} + linesMetric={MetricKey.lines} + showRequired={!isApp} /> - + </StyleMeasuresCard> + <StyleMeasuresCard> <MeasuresCardNumber label={ securityHotspots === '1' @@ -147,7 +230,7 @@ export default function OverallCodeMeasuresPanel(props: Readonly<OverallCodeMeas ) } /> - </div> - </div> + </StyleMeasuresCard> + </GridContainer> ); } diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateConditions.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateConditions.tsx index 4899399f211..51d9dd85820 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateConditions.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateConditions.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, Link } from 'design-system'; +import { CardSeparator, Link } from 'design-system'; import { sortBy } from 'lodash'; import * as React from 'react'; import { MetricKey } from '~sonar-aligned/types/metrics'; @@ -88,7 +88,7 @@ export function QualityGateConditions(props: Readonly<QualityGateConditionsProps condition={condition} /> )} - <BasicSeparator /> + <CardSeparator /> </div> ))} {renderCollapsed && ( diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx index 6da82da5ca3..985cfa72f48 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx @@ -17,9 +17,12 @@ * 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, Card, Spinner } from 'design-system'; +import { Card, CardSeparator, Spinner, TextError } from 'design-system'; import * as React from 'react'; +import { FormattedMessage } from 'react-intl'; import { ComponentQualifier } from '~sonar-aligned/types/component'; +import { translate } from '../../../helpers/l10n'; +import { isDiffMetric } from '../../../helpers/measures'; import { isApplication } from '../../../types/component'; import { QualityGateStatus } from '../../../types/quality-gates'; import { CaycStatus, Component, QualityGate } from '../../../types/types'; @@ -27,31 +30,38 @@ import IgnoredConditionWarning from '../components/IgnoredConditionWarning'; import ApplicationNonCaycProjectWarning from './ApplicationNonCaycProjectWarning'; import CleanAsYouCodeWarning from './CleanAsYouCodeWarning'; import QualityGatePanelSection from './QualityGatePanelSection'; -import QualityGateStatusPassedView from './QualityGateStatusPassedView'; +import SonarLintPromotion from './SonarLintPromotion'; export interface QualityGatePanelProps { component: Pick<Component, 'key' | 'qualifier' | 'qualityGate'>; + isNewCode?: boolean; loading?: boolean; qgStatuses?: QualityGateStatus[]; qualityGate?: QualityGate; + showCaycWarningInApp: boolean; + showCaycWarningInProject: boolean; + totalFailedConditionLength: number; } export function QualityGatePanel(props: QualityGatePanelProps) { - const { component, loading, qgStatuses = [], qualityGate } = props; + const { + component, + loading, + qgStatuses = [], + qualityGate, + isNewCode = false, + totalFailedConditionLength, + showCaycWarningInProject, + showCaycWarningInApp, + } = props; if (qgStatuses === undefined) { return null; } - const overallLevel = qgStatuses.map((s) => s.status).includes('ERROR') ? 'ERROR' : 'OK'; - const success = overallLevel === 'OK'; - const failedQgStatuses = qgStatuses.filter((qgStatus) => qgStatus.failedConditions.length > 0); - const overallFailedConditionsCount = qgStatuses.reduce( - (acc, qgStatus) => acc + qgStatus.failedConditions.length, - 0, - ); + const totalFailedCondition = qgStatuses?.flatMap((qg) => qg.failedConditions) ?? []; const isApp = isApplication(component.qualifier); @@ -66,43 +76,62 @@ export function QualityGatePanel(props: QualityGatePanelProps) { qgStatuses.some((p) => Boolean(p.ignoredConditions)); return ( - <div data-testid="overview__quality-gate-panel"> - <div className="sw-pt-5"> + <div data-testid="overview__quality-gate-panel-conditions"> + <div> <Spinner loading={loading}> - {success && <QualityGateStatusPassedView />} - - {showIgnoredConditionWarning && <IgnoredConditionWarning />} - - {!success && <BasicSeparator />} + {showIgnoredConditionWarning && isNewCode && <IgnoredConditionWarning />} + + {isApp && ( + <> + <TextError + className="sw-mb-3" + text={ + <FormattedMessage + defaultMessage={translate('quality_gates.conditions.x_conditions_failed')} + id="quality_gates.conditions.x_conditions_failed" + values={{ + conditions: totalFailedConditionLength, + }} + /> + } + /> + <CardSeparator /> + </> + )} - {overallFailedConditionsCount > 0 && ( + {totalFailedCondition.length > 0 && ( <div data-test="overview__quality-gate-conditions"> - {failedQgStatuses.map((qgStatus, qgStatusIdx) => ( - <QualityGatePanelSection - isApplication={isApp} - isLastStatus={qgStatusIdx === failedQgStatuses.length - 1} - key={qgStatus.key} - qgStatus={qgStatus} - qualityGate={qualityGate} - /> - ))} + {failedQgStatuses.map((qgStatus, qgStatusIdx) => { + const failedConditionLength = qgStatus.failedConditions.filter((con) => + isNewCode ? isDiffMetric(con.metric) : !isDiffMetric(con.metric), + ).length; + if (failedConditionLength > 0) { + return ( + <QualityGatePanelSection + isApplication={isApp} + isLastStatus={qgStatusIdx === failedQgStatuses.length - 1} + key={qgStatus.key} + qgStatus={qgStatus} + qualityGate={qualityGate} + isNewCode={isNewCode} + /> + ); + } + })} </div> )} </Spinner> </div> - {nonCaycProjectsInApp.length > 0 && ( - <ApplicationNonCaycProjectWarning projects={nonCaycProjectsInApp} /> + {showCaycWarningInApp && <ApplicationNonCaycProjectWarning projects={nonCaycProjectsInApp} />} + + {showCaycWarningInProject && ( + <Card className="sw-mt-4 sw-body-sm"> + <CleanAsYouCodeWarning component={component} /> + </Card> )} - {qgStatuses.length === 1 && - qgStatuses[0].caycStatus === CaycStatus.NonCompliant && - qualityGate?.actions?.manageConditions && - !isApp && ( - <Card className="sw-mt-4 sw-body-sm"> - <CleanAsYouCodeWarning component={component} /> - </Card> - )} + <SonarLintPromotion qgConditions={qgStatuses?.flatMap((qg) => qg.failedConditions)} /> </div> ); } diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanelSection.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanelSection.tsx index d591c286381..df91989c4a8 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanelSection.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanelSection.tsx @@ -17,125 +17,33 @@ * 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, BorderlessAccordion, TextMuted } from 'design-system'; +import { BorderlessAccordion, CardSeparator } from 'design-system'; import * as React from 'react'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { isDiffMetric } from '../../../helpers/measures'; -import { BranchLike } from '../../../types/branch-like'; -import { - QualityGateStatus, - QualityGateStatusConditionEnhanced, -} from '../../../types/quality-gates'; +import { translateWithParameters } from '../../../helpers/l10n'; +import { QualityGateStatus } from '../../../types/quality-gates'; import { QualityGate } from '../../../types/types'; -import ZeroNewIssuesSimplificationGuide from '../components/ZeroNewIssuesSimplificationGuide'; -import QualityGateConditions from './QualityGateConditions'; +import FailedConditions from './FailedConditions'; export interface QualityGatePanelSectionProps { - branchLike?: BranchLike; isApplication?: boolean; isLastStatus?: boolean; + isNewCode: boolean; qgStatus: QualityGateStatus; qualityGate?: QualityGate; } -function splitConditions( - conditions: QualityGateStatusConditionEnhanced[], -): [QualityGateStatusConditionEnhanced[], QualityGateStatusConditionEnhanced[]] { - const newCodeFailedConditions = []; - const overallFailedConditions = []; - - for (const condition of conditions) { - if (isDiffMetric(condition.metric)) { - newCodeFailedConditions.push(condition); - } else { - overallFailedConditions.push(condition); - } - } - - return [newCodeFailedConditions, overallFailedConditions]; -} - export function QualityGatePanelSection(props: QualityGatePanelSectionProps) { - const { isApplication, isLastStatus, qgStatus, qualityGate } = props; + const { isApplication, isLastStatus, qgStatus, qualityGate, isNewCode } = props; const [collapsed, setCollapsed] = React.useState(false); const toggle = React.useCallback(() => { setCollapsed(!collapsed); }, [collapsed]); - const [newCodeFailedConditions, overallFailedConditions] = splitConditions( - qgStatus.failedConditions, - ); - - const showSectionTitles = - isApplication || (overallFailedConditions.length > 0 && newCodeFailedConditions.length > 0); - const toggleLabel = collapsed ? translateWithParameters('overview.quality_gate.show_project_conditions_x', qgStatus.name) : translateWithParameters('overview.quality_gate.hide_project_conditions_x', qgStatus.name); - const newCodeText = - newCodeFailedConditions.length === 1 - ? translate('quality_gates.conditions.new_code_1') - : translateWithParameters( - 'quality_gates.conditions.new_code_x', - newCodeFailedConditions.length.toString(), - ); - - const overallText = - overallFailedConditions.length === 1 - ? translate('quality_gates.conditions.overall_code_1') - : translateWithParameters( - 'quality_gates.conditions.overall_code_x', - overallFailedConditions.length.toString(), - ); - - const renderFailedConditions = () => { - return ( - <> - {newCodeFailedConditions.length > 0 && ( - <> - {showSectionTitles && ( - <> - <p className="sw-px-2 sw-py-3">{newCodeText}</p> - - <BasicSeparator /> - </> - )} - - {qualityGate?.isBuiltIn && ( - <ZeroNewIssuesSimplificationGuide qualityGate={qualityGate} /> - )} - <QualityGateConditions - component={qgStatus} - branchLike={qgStatus.branchLike} - failedConditions={newCodeFailedConditions} - isBuiltInQualityGate={qualityGate?.isBuiltIn} - /> - </> - )} - - {overallFailedConditions.length > 0 && ( - <> - {showSectionTitles && ( - <> - <p className="sw-px-2 sw-py-3">{overallText}</p> - - <BasicSeparator /> - </> - )} - - <QualityGateConditions - component={qgStatus} - branchLike={qgStatus.branchLike} - failedConditions={overallFailedConditions} - /> - </> - )} - </> - ); - }; - return ( <> {isApplication ? ( @@ -147,26 +55,28 @@ export function QualityGatePanelSection(props: QualityGatePanelSectionProps) { header={ <div className="sw-flex sw-flex-col sw-text-sm"> <span className="sw-body-sm-highlight">{qgStatus.name}</span> - - {collapsed && newCodeFailedConditions.length > 0 && ( - <TextMuted text={newCodeText} /> - )} - - {collapsed && overallFailedConditions.length > 0 && ( - <TextMuted text={overallText} /> - )} </div> } > - <BasicSeparator /> + <CardSeparator /> - {renderFailedConditions()} + <FailedConditions + isNewCode={isNewCode} + isApplication={isApplication} + qualityGate={qualityGate} + qgStatus={qgStatus} + /> </BorderlessAccordion> - {(!isLastStatus || collapsed) && <BasicSeparator />} + {(!isLastStatus || collapsed) && <CardSeparator />} </> ) : ( - renderFailedConditions() + <FailedConditions + isNewCode={isNewCode} + isApplication={isApplication} + qualityGate={qualityGate} + qgStatus={qgStatus} + /> )} </> ); diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusPassedView.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusPassedView.tsx deleted file mode 100644 index 2a60ea651cb..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusPassedView.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { OverviewQGPassedIcon } from 'design-system'; -import React from 'react'; -import { translate } from '../../../helpers/l10n'; - -export default function QualityGateStatusPassedView() { - return ( - <div className="sw-flex sw-items-center sw-justify-center sw-flex-col"> - <OverviewQGPassedIcon className="sw-my-12" /> - <p className="sw-mb-8">{translate('overview.passed.clean_code')}</p> - </div> - ); -} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx index 10e6ca40957..420f7e65e9e 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx @@ -19,7 +19,7 @@ */ import styled from '@emotion/styled'; import { LinkHighlight, LinkStandalone, Tooltip } from '@sonarsource/echoes-react'; -import { Badge, LightGreyCard, LightGreyCardTitle, TextBold, TextSubdued } from 'design-system'; +import { Badge, TextBold, TextSubdued } from 'design-system'; import * as React from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import { formatMeasure } from '~sonar-aligned/helpers/measures'; @@ -92,18 +92,18 @@ export function SoftwareImpactMeasureCard(props: Readonly<SoftwareImpactBreakdow const failed = conditions.some((c) => c.level === Status.ERROR && c.metric === ratingMetricKey); return ( - <LightGreyCard + <div data-testid={`overview__software-impact-card-${softwareQuality}`} - className="sw-w-1/3 sw-overflow-hidden sw-rounded-2 sw-p-4 sw-flex-col" + className="sw-overflow-hidden sw-rounded-2 sw-flex-col" > - <LightGreyCardTitle> + <div className="sw-flex sw-items-center"> <TextBold name={intl.formatMessage({ id: `software_quality.${softwareQuality}` })} /> {failed && ( - <Badge className="sw-h-fit" variant="deleted"> + <Badge className="sw-h-fit sw-ml-2" variant="deleted"> <FormattedMessage id="overview.measures.failed_badge" /> </Badge> )} - </LightGreyCardTitle> + </div> <div className="sw-flex sw-flex-col sw-gap-3"> <div className="sw-flex sw-mt-4"> <div className="sw-flex sw-gap-1 sw-items-center"> @@ -164,7 +164,7 @@ export function SoftwareImpactMeasureCard(props: Readonly<SoftwareImpactBreakdow </div> )} </div> - </LightGreyCard> + </div> ); } diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx index 48e63751198..8d260d5fe93 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx @@ -142,7 +142,6 @@ describe('project overview', () => { // QG panel expect(screen.getByText('metric.level.OK')).toBeInTheDocument(); - expect(screen.getByText('overview.passed.clean_code')).toBeInTheDocument(); expect( screen.queryByText('overview.quality_gate.conditions.cayc.warning'), ).not.toBeInTheDocument(); @@ -253,7 +252,6 @@ describe('project overview', () => { renderBranchOverview(); expect(await screen.findByText('metric.level.ERROR')).toBeInTheDocument(); - expect(screen.getByText(/overview.X_conditions_failed/)).toBeInTheDocument(); expect(screen.getAllByText(/overview.quality_gate.required_x/)).toHaveLength(3); expect( screen.getByRole('link', { @@ -616,7 +614,7 @@ describe('application overview', () => { name: 'overview.quality_gate.hide_project_conditions_x.fourth project', }).get(), ).toBeInTheDocument(); - expect(byText('quality_gates.conditions.new_code_1').get()).toBeInTheDocument(); + expect(byText(/quality_gates.conditions.x_conditions_failed/).get()).toBeInTheDocument(); expect(byText('1 metric.new_violations.name').get()).toBeInTheDocument(); }); 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 d0820d64bb6..af61e9ccd75 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 @@ -80,11 +80,17 @@ const qgStatus = mockQualityGateStatus({ status: 'ERROR' as Status, }); -it('should render correctly for an application with 1 new code condition and 1 overall code condition', async () => { +it('should render correctly for an application for new code section', async () => { renderQualityGatePanelSection(); - expect(await screen.findByText('quality_gates.conditions.new_code_x.2')).toBeInTheDocument(); - expect(await screen.findByText('quality_gates.conditions.overall_code_1')).toBeInTheDocument(); + expect(await screen.findByText('metric.new_coverage.name')).toBeInTheDocument(); + expect(screen.getByText('metric.new_violations.name')).toBeInTheDocument(); +}); + +it('should render correctly for an application for overall code section', async () => { + renderQualityGatePanelSection({ isNewCode: false }); + + expect(await screen.findByText('metric.security_hotspots.name')).toBeInTheDocument(); }); it('should render correctly for a project with 1 new code condition', () => { @@ -130,7 +136,7 @@ function renderQualityGatePanelSection( ) { return renderComponent( <CurrentUserContextProvider currentUser={currentUser}> - <QualityGatePanelSection isApplication qgStatus={qgStatus} {...props} /> + <QualityGatePanelSection isApplication qgStatus={qgStatus} isNewCode {...props} /> </CurrentUserContextProvider>, ); } diff --git a/server/sonar-web/src/main/js/apps/overview/components/MeasuresCard.tsx b/server/sonar-web/src/main/js/apps/overview/components/MeasuresCard.tsx index 69355b2e1fe..d4cad7eba00 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/MeasuresCard.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/MeasuresCard.tsx @@ -19,7 +19,7 @@ */ import styled from '@emotion/styled'; import { LinkHighlight, LinkStandalone } from '@sonarsource/echoes-react'; -import { Badge, Card, themeBorder, themeColor } from 'design-system'; +import { Badge, themeColor } from 'design-system'; import * as React from 'react'; import { To } from 'react-router-dom'; import { MetricKey } from '~sonar-aligned/types/metrics'; @@ -38,10 +38,10 @@ export interface MeasuresCardProps { export default function MeasuresCard( props: React.PropsWithChildren<MeasuresCardProps & React.HTMLAttributes<HTMLDivElement>>, ) { - const { failed, children, metric, icon, value, url, label, ...rest } = props; + const { failed, children, metric, icon, value, url, label } = props; return ( - <StyledCard className="sw-p-6 sw-rounded-2 sw-text-base" {...rest}> + <div> <ColorBold className="sw-body-sm-highlight">{translate(label)}</ColorBold> {failed && ( <Badge className="sw-mt-1/2 sw-px-1 sw-ml-2" variant="deleted"> @@ -69,14 +69,10 @@ export default function MeasuresCard( {icon} </div> {children && <div className="sw-flex sw-flex-col">{children}</div>} - </StyledCard> + </div> ); } -const StyledCard = styled(Card)` - border: ${themeBorder('default')}; -`; - const ColorBold = styled.span` color: ${themeColor('pageTitle')}; `; |