From 5ff3751aa69b21ac0269eb9bc1b54625d42ecc08 Mon Sep 17 00:00:00 2001 From: Revanshu Paliwal Date: Thu, 13 Jun 2024 14:13:40 +0200 Subject: [PATCH] SONAR-22385 Updating layout for new and overall code inside project overview --- .../branches/BranchOverviewRenderer.tsx | 12 +- .../overview/branches/BranchSummaryStyles.tsx | 69 +++++ .../overview/branches/FailedConditions.tsx | 80 +++++ .../branches/MeasuresPanelPercentCards.tsx | 91 ------ .../branches/NewCodeMeasuresPanel.tsx | 286 ++++++++++++------ .../branches/OverallCodeMeasuresPanel.tsx | 117 +++++-- .../branches/QualityGateConditions.tsx | 4 +- .../overview/branches/QualityGatePanel.tsx | 103 ++++--- .../branches/QualityGatePanelSection.tsx | 130 ++------ .../branches/QualityGateStatusPassedView.tsx | 31 -- .../branches/SoftwareImpactMeasureCard.tsx | 14 +- .../branches/__tests__/BranchOverview-it.tsx | 4 +- .../QualityGatePanelSection-test.tsx | 14 +- .../apps/overview/components/MeasuresCard.tsx | 12 +- .../resources/org/sonar/l10n/core.properties | 2 +- 15 files changed, 566 insertions(+), 403 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/BranchSummaryStyles.tsx create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/FailedConditions.tsx delete mode 100644 server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelPercentCards.tsx delete mode 100644 server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusPassedView.tsx 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 ); @@ -222,7 +221,10 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp )} -
+
@@ -243,6 +245,10 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp branch={branch} component={component} measures={measures} + appLeak={appLeak} + period={period} + 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 && ( + <> + + } + /> + + + )} + {qualityGate?.isBuiltIn && isNewCode && ( + + )} + + + ); +} 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) { - const { useDiffMetric, branch, component, measures, conditions } = props; - - const isApp = isApplication(component.qualifier); - - return ( - <> - - - - - ); -} 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) { - 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) { ); } - return ( -
- - } - footer={issuesFooter} - /> - - - } - /> - + const leakPeriod = isApp ? appLeak : period; - + 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; - + {leakPeriod && ( + + {translate('overview.new_code')}: + + + + + )} + + {!noConditionsAndWarningForNewCode && ( + + - ) : ( - - ) - } - /> + + )} + + } + footer={issuesFooter} + /> + + + + } + /> + + + + + + + + + + ) : ( + + ) + } + /> + +
); } -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) { - 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 ( -
-
+ + {!noConditionsAndWarningForOverallCode && ( + + + + )} + + + + + -
- -
+ + - - + + + + + - + + -
-
+ + ); } 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 )} - +
))} {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; + 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 ( -
-
+
+
- {success && } - - {showIgnoredConditionWarning && } - - {!success && } + {showIgnoredConditionWarning && isNewCode && } + + {isApp && ( + <> + + } + /> + + + )} - {overallFailedConditionsCount > 0 && ( + {totalFailedCondition.length > 0 && (
- {failedQgStatuses.map((qgStatus, qgStatusIdx) => ( - - ))} + {failedQgStatuses.map((qgStatus, qgStatusIdx) => { + const failedConditionLength = qgStatus.failedConditions.filter((con) => + isNewCode ? isDiffMetric(con.metric) : !isDiffMetric(con.metric), + ).length; + if (failedConditionLength > 0) { + return ( + + ); + } + })}
)}
- {nonCaycProjectsInApp.length > 0 && ( - + {showCaycWarningInApp && } + + {showCaycWarningInProject && ( + + + )} - {qgStatuses.length === 1 && - qgStatuses[0].caycStatus === CaycStatus.NonCompliant && - qualityGate?.actions?.manageConditions && - !isApp && ( - - - - )} + qg.failedConditions)} />
); } 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 && ( - <> -

{newCodeText}

- - - - )} - - {qualityGate?.isBuiltIn && ( - - )} - - - )} - - {overallFailedConditions.length > 0 && ( - <> - {showSectionTitles && ( - <> -

{overallText}

- - - - )} - - - - )} - - ); - }; - return ( <> {isApplication ? ( @@ -147,26 +55,28 @@ export function QualityGatePanelSection(props: QualityGatePanelSectionProps) { header={
{qgStatus.name} - - {collapsed && newCodeFailedConditions.length > 0 && ( - - )} - - {collapsed && overallFailedConditions.length > 0 && ( - - )}
} > - + - {renderFailedConditions()} + - {(!isLastStatus || collapsed) && } + {(!isLastStatus || collapsed) && } ) : ( - renderFailedConditions() + )} ); 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 ( -
- -

{translate('overview.passed.clean_code')}

-
- ); -} 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 c.level === Status.ERROR && c.metric === ratingMetricKey); return ( - - +
{failed && ( - + )} - +
@@ -164,7 +164,7 @@ export function SoftwareImpactMeasureCard(props: Readonly )}
- +
); } 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( - + , ); } 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>, ) { - const { failed, children, metric, icon, value, url, label, ...rest } = props; + const { failed, children, metric, icon, value, url, label } = props; return ( - +
{translate(label)} {failed && ( @@ -69,14 +69,10 @@ export default function MeasuresCard( {icon}
{children &&
{children}
} -
+
); } -const StyledCard = styled(Card)` - border: ${themeBorder('default')}; -`; - const ColorBold = styled.span` color: ${themeColor('pageTitle')}; `; 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 13095128163..d23ae392df9 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -2283,6 +2283,7 @@ quality_gates.conditions.overall_code.long=Conditions on Overall Code quality_gates.conditions.overall_code.description=Conditions on overall code apply to branches only. quality_gates.conditions.overall_code_1=1 condition failed on overall code quality_gates.conditions.overall_code_x={0} conditions failed on overall code +quality_gates.conditions.x_conditions_failed={conditions} {conditions, plural, one {condition} other {conditions}} failed quality_gates.conditions.operator=Operator quality_gates.conditions.value=Value quality_gates.conditions.where=Where? @@ -3908,7 +3909,6 @@ overview.quality_gate_x=Quality Gate: {0} overview.quality_gate.help=A Quality Gate is a set of measure-based Boolean conditions. It helps you know immediately whether your project is production-ready. If your current status is not Passed, you'll see which measures caused the problem and the values required to pass. overview.quality_gate_failed_with_x=with {0} errors overview.quality_gate_code_clean=Your code is clean! -overview.passed.clean_code=Enjoy your sparkling clean code! overview.you_should_define_quality_gate=You should define a quality gate on this project. overview.quality_gate.ignored_conditions=Some Quality Gate conditions on New Code were ignored because of the small number of New Lines overview.quality_gate.ignored_conditions.tooltip=At the start of a new code period, if very few lines have been added or modified, it might be difficult to reach the desired level of code coverage or duplications. To prevent Quality Gate failure when there's little that can be done about it, Quality Gate conditions about duplications in new code and coverage on new code are ignored until the number of new lines is at least 20. An administrator can disable this in the general settings. -- 2.39.5