From: 7PH Date: Sun, 28 Jan 2024 14:25:53 +0000 (+0100) Subject: SONAR-21455 Update overall tab secondary measures cards X-Git-Tag: 10.4.0.87286~56 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=ff18a5bce14b7b170ee6ddc203c547f099480c62;p=sonarqube.git SONAR-21455 Update overall tab secondary measures cards --- diff --git a/server/sonar-web/design-system/src/components/CoverageIndicator.tsx b/server/sonar-web/design-system/src/components/CoverageIndicator.tsx index 8f51514408f..9eeb58479b7 100644 --- a/server/sonar-web/design-system/src/components/CoverageIndicator.tsx +++ b/server/sonar-web/design-system/src/components/CoverageIndicator.tsx @@ -38,7 +38,7 @@ export function CoverageIndicator({ size = 'sm', value }: CoverageIndicatorProps const thickness = SIZE_TO_THICKNESS_MAPPING[size]; if (value === undefined) { - return ; + return ; } const themeRed = themeColor('coverageRed')({ theme }); diff --git a/server/sonar-web/design-system/src/components/DuplicationsIndicator.tsx b/server/sonar-web/design-system/src/components/DuplicationsIndicator.tsx index 75e5de0e360..f43cb8a7206 100644 --- a/server/sonar-web/design-system/src/components/DuplicationsIndicator.tsx +++ b/server/sonar-web/design-system/src/components/DuplicationsIndicator.tsx @@ -35,7 +35,7 @@ export function DuplicationsIndicator({ size = 'sm', rating }: Props) { const sizePX = SIZE_TO_PX_MAPPING[size]; if (rating === undefined) { - return ; + return ; } const primaryColor = themeColor(`duplicationsIndicator.${rating}`)({ theme }); diff --git a/server/sonar-web/design-system/src/components/icons/NoDataIcon.tsx b/server/sonar-web/design-system/src/components/icons/NoDataIcon.tsx index afd93e23d3c..b746b9da8ff 100644 --- a/server/sonar-web/design-system/src/components/icons/NoDataIcon.tsx +++ b/server/sonar-web/design-system/src/components/icons/NoDataIcon.tsx @@ -19,9 +19,25 @@ */ import { CustomIcon, IconProps } from './Icon'; -export function NoDataIcon({ fill = 'currentColor', ...iconProps }: IconProps) { +export interface NoDataIconProps extends IconProps { + size?: 'xs' | 'sm' | 'md'; +} + +const SIZES: Record, number> = { + xs: 16, + sm: 24, + md: 36, +}; + +export function NoDataIcon({ + fill = 'currentColor', + size = 'md', + ...iconProps +}: Readonly) { + const iconSize = SIZES[size]; + return ( - + ) { - const { branch, component, measures = [], isNewCode, loading } = props; - const intl = useIntl(); - - const acceptedIssuesUrl = getComponentIssuesUrl(component.key, { - ...getBranchLikeQuery(branch), - issueStatuses: IssueStatus.Accepted, - ...(isNewCode ? { inNewCodePeriod: 'true' } : {}), - }); - - const acceptedIssuesWithHighImpactUrl = getComponentIssuesUrl(component.key, { - ...getBranchLikeQuery(branch), - ...DEFAULT_ISSUES_QUERY, - issueStatuses: IssueStatus.Accepted, - impactSeverities: SoftwareImpactSeverity.High, - }); - - const acceptedCount = isNewCode - ? getLeakValue(findMeasure(measures, MetricKey.new_accepted_issues)) - : findMeasure(measures, MetricKey.accepted_issues)?.value; - - const acceptedWithHighImpactCount = isNewCode - ? undefined - : findMeasure(measures, MetricKey.high_impact_accepted_issues)?.value; - - return ( -
- - - {intl.formatMessage({ id: 'overview.accepted_issues.description' })} - - -
- - - } - /> - {!isNewCode && ( - <> - - - ) : ( - - ) - } - /> - - )} - -
-
-
- ); -} - -const StyledCardSeparator = styled.div` - width: 1px; - background-color: ${themeColor('projectCardBorder')}; -`; - -export default React.memo(AcceptedIssuesPanel); diff --git a/server/sonar-web/src/main/js/apps/overview/branches/BranchOverallCodePanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverallCodePanel.tsx deleted file mode 100644 index 3f9e7d9d7f0..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/branches/BranchOverallCodePanel.tsx +++ /dev/null @@ -1,56 +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 { SoftwareQuality } from '../../../types/clean-code-taxonomy'; -import { MetricKey } from '../../../types/metrics'; -import { Component, MeasureEnhanced } from '../../../types/types'; -import SoftwareImpactMeasureCard from './SoftwareImpactMeasureCard'; - -export interface BranchOverallCodePanelProps { - component: Component; - measures: MeasureEnhanced[]; -} - -export default function BranchOverallCodePanel(props: Readonly) { - const { component, measures } = props; - - return ( -
- - - -
- ); -} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx index eb6b0d9892d..89f06d9c6ec 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx @@ -54,7 +54,7 @@ import { Analysis, GraphType, MeasureHistory } from '../../../types/project-acti import { QualityGateStatus, QualityGateStatusCondition } from '../../../types/quality-gates'; import { Component, MeasureEnhanced, Metric, Period, QualityGate } from '../../../types/types'; import '../styles.css'; -import { BRANCH_OVERVIEW_METRICS, HISTORY_METRICS_LIST } from '../utils'; +import { BRANCH_OVERVIEW_METRICS, HISTORY_METRICS_LIST, Status } from '../utils'; import BranchOverviewRenderer from './BranchOverviewRenderer'; interface Props { @@ -187,12 +187,14 @@ export default class BranchOverview extends React.PureComponent { (results) => { if (this.mounted) { const qgStatuses = results - .map(({ measures = [], project, projectBranchLike }) => { + .map(({ measures = [], project, projectBranchLike }): QualityGateStatus => { const { key, name, status, caycStatus } = project; const conditions = extractStatusConditionsFromApplicationStatusChildProject(project); - const failedConditions = this.getFailedConditions(conditions, measures); + const enhancedConditions = this.getEnhancedConditions(conditions, measures); + const failedConditions = enhancedConditions.filter((c) => c.level !== Status.OK); return { + conditions: enhancedConditions, failedConditions, caycStatus, key, @@ -244,11 +246,13 @@ export default class BranchOverview extends React.PureComponent { if (this.mounted && measures) { const { ignoredConditions, caycStatus, status } = projectStatus; const conditions = extractStatusConditionsFromProjectStatus(projectStatus); - const failedConditions = this.getFailedConditions(conditions, measures); + const enhancedConditions = this.getEnhancedConditions(conditions, measures); + const failedConditions = enhancedConditions.filter((c) => c.level !== Status.OK); - const qgStatus = { + const qgStatus: QualityGateStatus = { ignoredConditions, caycStatus, + conditions: enhancedConditions, failedConditions, key, name, @@ -362,10 +366,12 @@ export default class BranchOverview extends React.PureComponent { ); }; - getFailedConditions = (conditions: QualityGateStatusCondition[], measures: MeasureEnhanced[]) => { + getEnhancedConditions = ( + conditions: QualityGateStatusCondition[], + measures: MeasureEnhanced[], + ) => { return ( conditions - .filter((c) => c.level !== 'OK') // Enhance them with Metric information, which will be needed // to render the conditions properly. .map((c) => enhanceConditionWithMeasure(c, measures)) 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 85d096cd755..4a2efcc260b 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 @@ -41,15 +41,13 @@ import { Component, MeasureEnhanced, Metric, Period, QualityGate } from '../../. import { AnalysisStatus } from '../components/AnalysisStatus'; import SonarLintPromotion from '../components/SonarLintPromotion'; import { MeasuresTabs } from '../utils'; -import AcceptedIssuesPanel from './AcceptedIssuesPanel'; import ActivityPanel from './ActivityPanel'; import BranchMetaTopBar from './BranchMetaTopBar'; -import BranchOverallCodePanel from './BranchOverallCodePanel'; import FirstAnalysisNextStepsNotif from './FirstAnalysisNextStepsNotif'; -import { MeasuresPanel } from './MeasuresPanel'; import MeasuresPanelNoNewCode from './MeasuresPanelNoNewCode'; import NewCodeMeasuresPanel from './NewCodeMeasuresPanel'; import NoCodeWarning from './NoCodeWarning'; +import OverallCodeMeasuresPanel from './OverallCodeMeasuresPanel'; import QualityGatePanel from './QualityGatePanel'; import { TabsPanel } from './TabsPanel'; @@ -173,14 +171,6 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp isNewCode={isNewCodeTab} onTabSelect={selectTab} > - {!hasNewCodeMeasures && isNewCodeTab && ( - - )} - {hasNewCodeMeasures && isMissingMeasures && isApplication(component.qualifier) && ( @@ -191,11 +181,15 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp )} - {!isNewCodeTab && ( - + {isNewCodeTab && !hasNewCodeMeasures && ( + )} - {hasNewCodeMeasures && isNewCodeTab && ( + {isNewCodeTab && hasNewCodeMeasures && ( )} - - - + {!isNewCodeTab && ( + + )} - {[IssueType.Bug, IssueType.CodeSmell, IssueType.Vulnerability, IssueType.SecurityHotspot].map( - (type: IssueType) => ( - - - - ), - )} - - {(findMeasure(measures, MetricKey.coverage) || - findMeasure(measures, MetricKey.new_coverage)) && ( - - - - )} - - - - - - ); -} - -export default React.memo(MeasuresPanel); - -function renderCoverageIcon(value?: string) { - return ; -} - -function renderDuplicationIcon(value?: string) { - const rating = value !== undefined ? duplicationRatingConverter(Number(value)) : undefined; - - return ; -} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelIssueMeasure.tsx b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelIssueMeasure.tsx deleted file mode 100644 index 5fbfd2018d6..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelIssueMeasure.tsx +++ /dev/null @@ -1,85 +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 { LightPrimary, ThemeColors } from 'design-system'; -import * as React from 'react'; -import { translate } from '../../../helpers/l10n'; -import { BranchLike } from '../../../types/branch-like'; -import { ComponentQualifier } from '../../../types/component'; -import { IssueType } from '../../../types/issues'; -import { Component, MeasureEnhanced } from '../../../types/types'; -import IssueLabel from '../components/IssueLabel'; -import IssueRating from '../components/IssueRating'; -import { getIssueIconClass, getIssueRatingName } from '../utils'; -import MeasuresPanelCard from './MeasuresPanelCard'; - -interface Props { - branchLike?: BranchLike; - component: Component; - isNewCodeTab: boolean; - measures: MeasureEnhanced[]; - type: IssueType; -} - -export default function MeasuresPanelIssueMeasure(props: Props) { - const { branchLike, component, isNewCodeTab, measures, type } = props; - - const isApp = component.qualifier === ComponentQualifier.Application; - - const IconClass = getIssueIconClass(type) as (args: { - className?: string; - fill?: ThemeColors; - }) => JSX.Element; - - return ( - - - {getIssueRatingName(type)} - - } - rating={ - !isApp || !isNewCodeTab ? ( - - ) : null - } - > - - - ); -} 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 new file mode 100644 index 00000000000..d8721359cdd --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelPercentCards.tsx @@ -0,0 +1,91 @@ +/* + * 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 { getComponentDrilldownUrl } from '../../../helpers/urls'; +import { BranchLike } from '../../../types/branch-like'; +import { isApplication } from '../../../types/component'; +import { MetricKey } from '../../../types/metrics'; +import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates'; +import { Component, MeasureEnhanced } from '../../../types/types'; +import MeasuresCardPercent from '../components/MeasuresCardPercent'; +import { MeasurementType, getMeasurementMetricKey } from '../utils'; + +interface Props { + useDiffMetric?: boolean; + branch?: BranchLike; + component: Component; + measures: MeasureEnhanced[]; + failedConditions: QualityGateStatusConditionEnhanced[]; +} + +/** + * Renders Coverage and Duplication cards for the Overview page. + */ +export default function MeasuresPanelPercentCards(props: Readonly) { + const { useDiffMetric, branch, component, measures, failedConditions } = props; + + const isApp = isApplication(component.qualifier); + + return ( +
+ + + +
+ ); +} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelPercentMeasure.tsx b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelPercentMeasure.tsx deleted file mode 100644 index 2e53262fbfa..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelPercentMeasure.tsx +++ /dev/null @@ -1,140 +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 { DrilldownLink, GreySeparator, LightLabel, LightPrimary } from 'design-system'; -import * as React from 'react'; -import { getLeakValue } from '../../../components/measure/utils'; -import { isPullRequest } from '../../../helpers/branch-like'; -import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n'; -import { findMeasure, formatMeasure, localizeMetric } from '../../../helpers/measures'; -import { getComponentDrilldownUrl } from '../../../helpers/urls'; -import { BranchLike } from '../../../types/branch-like'; -import { MetricKey, MetricType } from '../../../types/metrics'; -import { Component, MeasureEnhanced } from '../../../types/types'; -import AfterMergeEstimate from '../pullRequests/AfterMergeEstimate'; -import { MeasurementType, getMeasurementMetricKey } from '../utils'; -import DrilldownMeasureValue from './DrilldownMeasureValue'; -import MeasuresPanelCard from './MeasuresPanelCard'; -import MeasuresPanelPercentMeasureLabel from './MeasuresPanelPercentMeasureLabel'; - -interface Props { - branchLike?: BranchLike; - component: Component; - useDiffMetric: boolean; - measures: MeasureEnhanced[]; - ratingIcon: (value: string | undefined) => React.ReactElement; - secondaryMetricKey?: MetricKey; - type: MeasurementType; -} - -export default function MeasuresPanelPercentMeasure(props: Props) { - const { - branchLike, - component, - measures, - ratingIcon, - secondaryMetricKey, - type, - useDiffMetric = false, - } = props; - const metricKey = getMeasurementMetricKey(type, useDiffMetric); - const measure = findMeasure(measures, metricKey); - - let value; - if (measure) { - value = useDiffMetric ? getLeakValue(measure) : measure.value; - } - - const url = getComponentDrilldownUrl({ - componentKey: component.key, - metric: metricKey, - branchLike, - listView: true, - }); - - const formattedValue = formatMeasure(value, MetricType.Percent, { - decimals: 2, - omitExtraDecimalZeros: true, - }); - - return ( - {translate('overview.measurement_type', type)}} - rating={ratingIcon(value)} - > - <> -
- {value === undefined ? ( - — - ) : ( - - {formattedValue} - - )} - - - {translate('overview.measurement_type', type)} - -
- - - {!useDiffMetric && secondaryMetricKey && ( - <> - -
- - - {getLocalizedMetricName({ key: secondaryMetricKey })} - -
- - )} - - {isPullRequest(branchLike) && ( -
- - - - {translate('component_measures.facet_category.overall_category.estimated')} - -
- )} - -
- ); -} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelPercentMeasureLabel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelPercentMeasureLabel.tsx deleted file mode 100644 index c753796751b..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelPercentMeasureLabel.tsx +++ /dev/null @@ -1,74 +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 { DrilldownLink, LightLabel } from 'design-system'; -import * as React from 'react'; -import { FormattedMessage } from 'react-intl'; -import { getLeakValue } from '../../../components/measure/utils'; -import { translate } from '../../../helpers/l10n'; -import { findMeasure, formatMeasure } from '../../../helpers/measures'; -import { getComponentDrilldownUrl } from '../../../helpers/urls'; -import { BranchLike } from '../../../types/branch-like'; -import { MetricType } from '../../../types/metrics'; -import { Component, MeasureEnhanced } from '../../../types/types'; -import { MeasurementType, getMeasurementLabelKeys, getMeasurementLinesMetricKey } from '../utils'; - -interface Props { - branchLike?: BranchLike; - component: Component; - useDiffMetric: boolean; - measures: MeasureEnhanced[]; - type: MeasurementType; -} - -export default function MeasuresPanelPercentMeasureLabel(props: Props) { - const { branchLike, component, measures, type, useDiffMetric = false } = props; - const { expandedLabelKey, labelKey } = getMeasurementLabelKeys(type, useDiffMetric); - const linesMetric = getMeasurementLinesMetricKey(type, useDiffMetric); - const measure = findMeasure(measures, linesMetric); - - if (!measure) { - return {translate(labelKey)}; - } - - const value = useDiffMetric ? getLeakValue(measure) : measure.value; - - const url = getComponentDrilldownUrl({ - componentKey: component.key, - metric: linesMetric, - branchLike, - listView: true, - }); - - return ( - - - {formatMeasure(value, MetricType.ShortInteger)} - - ), - }} - /> - - ); -} 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 00a35b001d6..895a2846625 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 @@ -34,11 +34,7 @@ import { getLeakValue } from '../../../components/measure/utils'; import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { findMeasure, formatMeasure } from '../../../helpers/measures'; -import { - getComponentDrilldownUrl, - getComponentIssuesUrl, - getComponentSecurityHotspotsUrl, -} from '../../../helpers/urls'; +import { getComponentIssuesUrl, getComponentSecurityHotspotsUrl } from '../../../helpers/urls'; import { Branch } from '../../../types/branch-like'; import { isApplication } from '../../../types/component'; import { IssueStatus } from '../../../types/issues'; @@ -47,14 +43,8 @@ import { QualityGateStatus } from '../../../types/quality-gates'; import { Component, MeasureEnhanced } from '../../../types/types'; import { IssueMeasuresCardInner } from '../components/IssueMeasuresCardInner'; import MeasuresCardNumber from '../components/MeasuresCardNumber'; -import MeasuresCardPercent from '../components/MeasuresCardPercent'; -import { - MeasurementType, - MeasuresTabs, - Status, - getConditionRequiredLabel, - getMeasurementMetricKey, -} from '../utils'; +import { MeasuresTabs, Status, getConditionRequiredLabel } from '../utils'; +import MeasuresPanelPercentCards from './MeasuresPanelPercentCards'; interface Props { branch?: Branch; @@ -68,6 +58,7 @@ export default function NewCodeMeasuresPanel(props: Readonly) { const intl = useIntl(); const isApp = isApplication(component.qualifier); + const conditions = qgStatuses?.flatMap((qg) => qg.conditions) ?? []; const failedConditions = qgStatuses?.flatMap((qg) => qg.failedConditions) ?? []; const newIssues = getLeakValue(findMeasure(measures, MetricKey.new_violations)); @@ -158,45 +149,16 @@ export default function NewCodeMeasuresPanel(props: Readonly) { } /> -
- - + +
) { ...getBranchLikeQuery(branch), })} value={newSecurityHotspots} - conditions={failedConditions} + metric={MetricKey.new_security_hotspots} + conditions={conditions} conditionMetric={MetricKey.new_security_hotspots_reviewed} showRequired={!isApp} /> 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 new file mode 100644 index 00000000000..4c228520cc7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx @@ -0,0 +1,146 @@ +/* + * 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 { + MetricsRatingBadge, + NoDataIcon, + SnoozeCircleIcon, + TextSubdued, + getTabPanelId, +} from 'design-system'; +import * as React from 'react'; +import { useIntl } from 'react-intl'; +import { getBranchLikeQuery } from '../../../helpers/branch-like'; +import { findMeasure, formatMeasure, formatRating } from '../../../helpers/measures'; +import { getComponentIssuesUrl, getComponentSecurityHotspotsUrl } from '../../../helpers/urls'; +import { BranchLike } from '../../../types/branch-like'; +import { SoftwareQuality } from '../../../types/clean-code-taxonomy'; +import { isApplication } from '../../../types/component'; +import { IssueStatus } from '../../../types/issues'; +import { MetricKey, MetricType } from '../../../types/metrics'; +import { QualityGateStatus } from '../../../types/quality-gates'; +import { Component, MeasureEnhanced } from '../../../types/types'; +import MeasuresCard from '../components/MeasuresCard'; +import MeasuresCardNumber from '../components/MeasuresCardNumber'; +import { MeasuresTabs } from '../utils'; +import MeasuresPanelPercentCards from './MeasuresPanelPercentCards'; +import SoftwareImpactMeasureCard from './SoftwareImpactMeasureCard'; + +export interface OverallCodeMeasuresPanelProps { + branch?: BranchLike; + component: Component; + measures: MeasureEnhanced[]; + qgStatuses?: QualityGateStatus[]; +} + +export default function OverallCodeMeasuresPanel(props: Readonly) { + const { branch, qgStatuses, component, measures } = props; + + const intl = useIntl(); + + const isApp = isApplication(component.qualifier); + const conditions = qgStatuses?.flatMap((qg) => qg.conditions) ?? []; + const failedConditions = qgStatuses?.flatMap((qg) => qg.failedConditions) ?? []; + const acceptedIssues = findMeasure(measures, MetricKey.accepted_issues)?.value; + const securityHotspots = findMeasure(measures, MetricKey.security_hotspots)?.value; + const securityRating = findMeasure(measures, MetricKey.security_rating)?.value; + + return ( +
+
+ + + +
+ +
+ + } + > + + {intl.formatMessage({ + id: 'overview.accepted_issues.help_verbose', + })} + + + + + ) : ( + + ) + } + /> +
+ + +
+ ); +} diff --git a/server/sonar-web/src/main/js/apps/overview/components/IssueMeasuresCardInner.tsx b/server/sonar-web/src/main/js/apps/overview/components/IssueMeasuresCardInner.tsx index 8600556c2fa..3e47c0b0b37 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/IssueMeasuresCardInner.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/IssueMeasuresCardInner.tsx @@ -19,7 +19,7 @@ */ import styled from '@emotion/styled'; import classNames from 'classnames'; -import { Badge, ContentLink, themeColor } from 'design-system'; +import { Badge, ContentLink, NoDataIcon, themeColor } from 'design-system'; import * as React from 'react'; import { Path } from 'react-router-dom'; import Tooltip from '../../../components/controls/Tooltip'; @@ -82,7 +82,7 @@ export function IssueMeasuresCardInner(props: Readonly
- {icon} + {value ? icon : }
{footer} 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 f8b35111e22..de80ee18c20 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 @@ -25,9 +25,9 @@ import { translate, translateWithParameters } from '../../../helpers/l10n'; import { localizeMetric } from '../../../helpers/measures'; import { MetricKey } from '../../../types/metrics'; -interface Props { +export interface MeasuresCardProps { url: To; - value: string; + value?: string; metric: MetricKey; label: string; failed?: boolean; @@ -35,7 +35,7 @@ interface Props { } export default function MeasuresCard( - props: React.PropsWithChildren>, + props: React.PropsWithChildren>, ) { const { failed, children, metric, icon, value, url, label, ...rest } = props; diff --git a/server/sonar-web/src/main/js/apps/overview/components/MeasuresCardNumber.tsx b/server/sonar-web/src/main/js/apps/overview/components/MeasuresCardNumber.tsx index 1e3214186ea..1c48863a3f7 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/MeasuresCardNumber.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/MeasuresCardNumber.tsx @@ -25,13 +25,13 @@ import { formatMeasure } from '../../../helpers/measures'; import { MetricKey, MetricType } from '../../../types/metrics'; import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates'; import { Status, getConditionRequiredLabel } from '../utils'; -import MeasuresCard from './MeasuresCard'; +import MeasuresCard, { MeasuresCardProps } from './MeasuresCard'; -interface Props { +interface Props extends MeasuresCardProps { conditions: QualityGateStatusConditionEnhanced[]; label: string; url: To; - value: string; + value?: string; conditionMetric: MetricKey; showRequired?: boolean; } @@ -51,7 +51,6 @@ export default function MeasuresCardNumber( - <> - - {showRequired && - condition && - (conditionFailed ? ( - - ) : ( - {getConditionRequiredLabel(condition, intl)} - ))} - - -
- - - {formatMeasure(linesValue ?? '0', MetricType.ShortInteger)} - - ), - }} + + {showRequired && + condition && + (conditionFailed ? ( + - -
- + ) : ( + {getConditionRequiredLabel(condition, intl)} + ))} + + +
+ + + {formatMeasure(linesValue ?? '0', MetricType.ShortInteger)} + + ), + }} + /> + +
); } diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/AfterMergeEstimate.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/AfterMergeEstimate.tsx deleted file mode 100644 index 41e8b9e9487..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/pullRequests/AfterMergeEstimate.tsx +++ /dev/null @@ -1,52 +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 classNames from 'classnames'; -import { LightPrimary } from 'design-system'; -import * as React from 'react'; -import { findMeasure, formatMeasure } from '../../../helpers/measures'; -import { MetricType } from '../../../types/metrics'; -import { MeasureEnhanced } from '../../../types/types'; -import { MeasurementType, getMeasurementAfterMergeMetricKey } from '../utils'; - -export interface AfterMergeEstimateProps { - className?: string; - measures: MeasureEnhanced[]; - type: MeasurementType; -} - -export function AfterMergeEstimate({ className, measures, type }: AfterMergeEstimateProps) { - const afterMergeMetric = getMeasurementAfterMergeMetricKey(type); - - const measure = findMeasure(measures, afterMergeMetric); - - if (!measure || measure.value === undefined) { - return null; - } - - return ( -
- - {formatMeasure(measure.value, MetricType.Percent)} - -
- ); -} - -export default React.memo(AfterMergeEstimate); diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCardPanel.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCardPanel.tsx index 37a36ecb1e2..b67e517ca95 100644 --- a/server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCardPanel.tsx +++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCardPanel.tsx @@ -87,6 +87,7 @@ export default function MeasuresCardPanel(props: React.PropsWithChildren) ...getBranchLikeQuery(pullRequest), })} value={newSecurityHotspots} + metric={MetricKey.new_security_hotspots} conditions={conditions} conditionMetric={MetricKey.new_security_hotspots_reviewed} showRequired diff --git a/server/sonar-web/src/main/js/apps/overview/utils.tsx b/server/sonar-web/src/main/js/apps/overview/utils.tsx index 30c9e557766..4510c2641df 100644 --- a/server/sonar-web/src/main/js/apps/overview/utils.tsx +++ b/server/sonar-web/src/main/js/apps/overview/utils.tsx @@ -20,8 +20,6 @@ import { memoize } from 'lodash'; import React from 'react'; import { IntlShape } from 'react-intl'; -import CoverageRating from '../../components/ui/CoverageRating'; -import DuplicationsRating from '../../components/ui/DuplicationsRating'; import { ISSUETYPE_METRIC_KEYS_MAP } from '../../helpers/issues'; import { translate } from '../../helpers/l10n'; import { formatMeasure } from '../../helpers/measures'; @@ -156,24 +154,10 @@ const MEASUREMENTS_MAP = { [MeasurementType.Coverage]: { metric: MetricKey.coverage, newMetric: MetricKey.new_coverage, - linesMetric: MetricKey.lines_to_cover, - newLinesMetric: MetricKey.new_lines_to_cover, - afterMergeMetric: MetricKey.coverage, - labelKey: 'metric.coverage.name', - expandedLabelKey: 'overview.coverage_on_X_lines', - newLinesExpandedLabelKey: 'overview.coverage_on_X_new_lines', - iconClass: CoverageRating, }, [MeasurementType.Duplication]: { metric: MetricKey.duplicated_lines_density, newMetric: MetricKey.new_duplicated_lines_density, - linesMetric: MetricKey.ncloc, - newLinesMetric: MetricKey.new_lines, - afterMergeMetric: MetricKey.duplicated_lines_density, - labelKey: 'metric.duplicated_lines_density.short_name', - expandedLabelKey: 'overview.duplications_on_X_lines', - newLinesExpandedLabelKey: 'overview.duplications_on_X_new_lines', - iconClass: DuplicationsRating, }, }; @@ -223,10 +207,6 @@ export function getIssueRatingName(type: IssueType) { return translate('metric_domain', ISSUETYPE_METRIC_KEYS_MAP[type].ratingName); } -export function getIssueIconClass(type: IssueType) { - return ISSUETYPE_METRIC_KEYS_MAP[type].iconClass; -} - export function getIssueMetricKey(type: IssueType, useDiffMetric: boolean) { return useDiffMetric ? ISSUETYPE_METRIC_KEYS_MAP[type].newMetric @@ -239,31 +219,10 @@ export function getIssueRatingMetricKey(type: IssueType, useDiffMetric: boolean) : ISSUETYPE_METRIC_KEYS_MAP[type].rating; } -export function getMeasurementIconClass(type: MeasurementType) { - return MEASUREMENTS_MAP[type].iconClass; -} - export function getMeasurementMetricKey(type: MeasurementType, useDiffMetric: boolean) { return useDiffMetric ? MEASUREMENTS_MAP[type].newMetric : MEASUREMENTS_MAP[type].metric; } -export function getMeasurementAfterMergeMetricKey(type: MeasurementType) { - return MEASUREMENTS_MAP[type].afterMergeMetric; -} - -export function getMeasurementLinesMetricKey(type: MeasurementType, useDiffMetric: boolean) { - return useDiffMetric ? MEASUREMENTS_MAP[type].newLinesMetric : MEASUREMENTS_MAP[type].linesMetric; -} - -export function getMeasurementLabelKeys(type: MeasurementType, useDiffMetric: boolean) { - return { - expandedLabelKey: useDiffMetric - ? MEASUREMENTS_MAP[type].newLinesExpandedLabelKey - : MEASUREMENTS_MAP[type].expandedLabelKey, - labelKey: MEASUREMENTS_MAP[type].labelKey, - }; -} - export const parseQuery = memoize((urlQuery: RawQuery): { codeScope: string } => { return { codeScope: parseAsString(urlQuery['code_scope']), diff --git a/server/sonar-web/src/main/js/helpers/mocks/quality-gates.ts b/server/sonar-web/src/main/js/helpers/mocks/quality-gates.ts index bda28e1abe2..f44e2396b24 100644 --- a/server/sonar-web/src/main/js/helpers/mocks/quality-gates.ts +++ b/server/sonar-web/src/main/js/helpers/mocks/quality-gates.ts @@ -39,10 +39,12 @@ export function mockQualityGate(overrides: Partial = {}): QualityGa export function mockQualityGateStatus( overrides: Partial = {}, ): QualityGateStatus { + const condition = mockQualityGateStatusConditionEnhanced(); return { ignoredConditions: false, caycStatus: CaycStatus.Compliant, - failedConditions: [mockQualityGateStatusConditionEnhanced()], + conditions: [condition], + failedConditions: [condition], key: 'foo', name: 'Foo', status: 'ERROR', diff --git a/server/sonar-web/src/main/js/types/quality-gates.ts b/server/sonar-web/src/main/js/types/quality-gates.ts index e6084098fda..8a7701bec6f 100644 --- a/server/sonar-web/src/main/js/types/quality-gates.ts +++ b/server/sonar-web/src/main/js/types/quality-gates.ts @@ -64,6 +64,7 @@ export interface QualityGateApplicationStatusChildProject { } export interface QualityGateStatus { + conditions: QualityGateStatusConditionEnhanced[]; failedConditions: QualityGateStatusConditionEnhanced[]; ignoredConditions?: boolean; caycStatus: CaycStatus; 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 45b83dc14ce..cfb318747f8 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -977,10 +977,10 @@ issue.type.CODE_SMELL=Code Smell issue.type.BUG=Bug issue.type.VULNERABILITY=Vulnerability issue.type.SECURITY_HOTSPOT=Security Hotspot +issue.type.SECURITY_HOTSPOT.plural=Security Hotspots issue.type.CODE_SMELL.plural=Code Smells issue.type.BUG.plural=Bugs issue.type.VULNERABILITY.plural=Vulnerabilities -issue.type.SECURITY_HOTSPOT.plural=Security Hotspots issue.type.deprecation.title=Issue types are deprecated and can no longer be modified. issue.type.deprecation.filter_by=You can now filter issues by: @@ -3861,6 +3861,7 @@ overview.pull_request.fixed_issues.disclaimer=Only issues fixed on the files mod overview.pull_request.fixed_issues.disclaimer.2=When the pull request and the target branch are not synchronized, issues introduced on the target branch may be incorrectly considered fixed by the pull request. Rebasing the pull request would give an updated value. overview.accepted_issues=Accepted issues overview.accepted_issues.help=Valid issues that were not fixed +overview.accepted_issues.help_verbose=Valid issues, but not fixed. They represent accepted technical debt. overview.quality_gate.status=Quality Gate Status overview.quality_gate=Quality Gate overview.quality_gate_x=Quality Gate: {0}