From: 7PH Date: Mon, 29 Jan 2024 13:21:35 +0000 (+0100) Subject: SONAR-21455 Drop dead code & Re-organize branch/pr overview components to relevant... X-Git-Tag: 10.4.0.87286~54 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=5e66c60c4cc35a6023413324f56c46411e326d54;p=sonarqube.git SONAR-21455 Drop dead code & Re-organize branch/pr overview components to relevant folders --- diff --git a/server/sonar-web/design-system/src/theme/light.ts b/server/sonar-web/design-system/src/theme/light.ts index d6ba2162d3b..dbce87280d1 100644 --- a/server/sonar-web/design-system/src/theme/light.ts +++ b/server/sonar-web/design-system/src/theme/light.ts @@ -532,7 +532,7 @@ export const lightTheme = { overviewCardSuccessIcon: COLORS.green[200], // overview software impact breakdown - overviewSoftwareImpactSeverityNeutral: [247, 249, 252], + overviewSoftwareImpactSeverityNeutral: COLORS.blueGrey[35], overviewSoftwareImpactSeverityHigh: COLORS.red[100], overviewSoftwareImpactSeverityMedium: COLORS.yellow[100], overviewSoftwareImpactSeverityLow: COLORS.blue[100], 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 4a2efcc260b..1a6e477c88b 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 @@ -39,7 +39,6 @@ import { Analysis, GraphType, MeasureHistory } from '../../../types/project-acti import { QualityGateStatus } from '../../../types/quality-gates'; import { Component, MeasureEnhanced, Metric, Period, QualityGate } from '../../../types/types'; import { AnalysisStatus } from '../components/AnalysisStatus'; -import SonarLintPromotion from '../components/SonarLintPromotion'; import { MeasuresTabs } from '../utils'; import ActivityPanel from './ActivityPanel'; import BranchMetaTopBar from './BranchMetaTopBar'; @@ -49,6 +48,7 @@ import NewCodeMeasuresPanel from './NewCodeMeasuresPanel'; import NoCodeWarning from './NoCodeWarning'; import OverallCodeMeasuresPanel from './OverallCodeMeasuresPanel'; import QualityGatePanel from './QualityGatePanel'; +import SonarLintPromotion from './SonarLintPromotion'; import { TabsPanel } from './TabsPanel'; export interface BranchOverviewRendererProps { diff --git a/server/sonar-web/src/main/js/apps/overview/branches/DebtValue.tsx b/server/sonar-web/src/main/js/apps/overview/branches/DebtValue.tsx deleted file mode 100644 index b719d2c281d..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/branches/DebtValue.tsx +++ /dev/null @@ -1,75 +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 { getLeakValue } from '../../../components/measure/utils'; -import DrilldownLink from '../../../components/shared/DrilldownLink'; -import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n'; -import { findMeasure, formatMeasure, localizeMetric } from '../../../helpers/measures'; -import { BranchLike } from '../../../types/branch-like'; -import { MetricKey } from '../../../types/metrics'; -import { Component, MeasureEnhanced } from '../../../types/types'; - -export interface DebtValueProps { - branchLike?: BranchLike; - component: Component; - measures: MeasureEnhanced[]; - useDiffMetric?: boolean; -} - -export function DebtValue(props: DebtValueProps) { - const { branchLike, component, measures, useDiffMetric = false } = props; - const metricKey = useDiffMetric ? MetricKey.new_technical_debt : MetricKey.sqale_index; - const measure = findMeasure(measures, metricKey); - - let value; - let metricName; - if (measure) { - value = useDiffMetric ? getLeakValue(measure) : measure.value; - metricName = getLocalizedMetricName(measure.metric, true); - } else { - metricName = localizeMetric(metricKey); - } - const formattedValue = formatMeasure(value, 'WORK_DUR'); - - return ( - <> - {value === undefined ? ( - - ) : ( - - {formattedValue} - - )} - {metricName} - - ); -} - -export default React.memo(DebtValue); diff --git a/server/sonar-web/src/main/js/apps/overview/branches/DrilldownMeasureValue.tsx b/server/sonar-web/src/main/js/apps/overview/branches/DrilldownMeasureValue.tsx deleted file mode 100644 index 97dfdb5c5dc..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/branches/DrilldownMeasureValue.tsx +++ /dev/null @@ -1,66 +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 } from 'design-system'; -import * as React from 'react'; -import { 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'; - -export interface DrilldownMeasureValueProps { - branchLike?: BranchLike; - component: Component; - measures: MeasureEnhanced[]; - metric: MetricKey; -} - -export function DrilldownMeasureValue(props: DrilldownMeasureValueProps) { - const { branchLike, component, measures, metric } = props; - const measure = findMeasure(measures, metric); - - if (!measure || measure.value === undefined) { - return –; - } - - const url = getComponentDrilldownUrl({ - branchLike, - componentKey: component.key, - metric, - }); - - return ( - - - {formatMeasure(measure.value, MetricType.ShortInteger)} - - - ); -} - -export default React.memo(DrilldownMeasureValue); diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelCard.tsx b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelCard.tsx deleted file mode 100644 index dc0dba16ea4..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelCard.tsx +++ /dev/null @@ -1,43 +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'; - -interface Props { - category: React.ReactElement; - rating: React.ReactElement | null; -} - -export default function MeasuresPanelCard( - props: React.PropsWithChildren>, -) { - const { category, children, rating, ...attributes } = props; - - return ( -
-
-
{category}
- -
{children}
-
- -
{rating}
-
- ); -} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx new file mode 100644 index 00000000000..dda75f63084 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx @@ -0,0 +1,182 @@ +/* + * 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 { LinkBox, TextMuted } from 'design-system'; +import * as React from 'react'; +import { Path } from 'react-router-dom'; +import IssueTypeIcon from '../../../components/icons/IssueTypeIcon'; +import MeasureIndicator from '../../../components/measure/MeasureIndicator'; +import { + DEFAULT_ISSUES_QUERY, + isIssueMeasure, + propsToIssueParams, +} from '../../../components/shared/utils'; +import { getBranchLikeQuery } from '../../../helpers/branch-like'; +import { translate } from '../../../helpers/l10n'; +import { formatMeasure, isDiffMetric, localizeMetric } from '../../../helpers/measures'; +import { getOperatorLabel } from '../../../helpers/qualityGates'; +import { + getComponentDrilldownUrl, + getComponentIssuesUrl, + getComponentSecurityHotspotsUrl, +} from '../../../helpers/urls'; +import { BranchLike } from '../../../types/branch-like'; +import { IssueType } from '../../../types/issues'; +import { MetricKey, MetricType } from '../../../types/metrics'; +import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates'; +import { Component, Dict } from '../../../types/types'; +import { RATING_TO_SEVERITIES_MAPPING } from '../utils'; + +interface Props { + branchLike?: BranchLike; + component: Pick; + condition: QualityGateStatusConditionEnhanced; +} + +export default class QualityGateCondition extends React.PureComponent { + getIssuesUrl = (inNewCodePeriod: boolean, customQuery: Dict) => { + const query: Dict = { + ...DEFAULT_ISSUES_QUERY, + ...getBranchLikeQuery(this.props.branchLike), + ...customQuery, + }; + if (inNewCodePeriod) { + Object.assign(query, { inNewCodePeriod: 'true' }); + } + return getComponentIssuesUrl(this.props.component.key, query); + }; + + getUrlForSecurityHotspot(inNewCodePeriod: boolean) { + const query: Dict = { + ...getBranchLikeQuery(this.props.branchLike), + }; + if (inNewCodePeriod) { + Object.assign(query, { inNewCodePeriod: 'true' }); + } + return getComponentSecurityHotspotsUrl(this.props.component.key, query); + } + + getUrlForCodeSmells(inNewCodePeriod: boolean) { + return this.getIssuesUrl(inNewCodePeriod, { types: 'CODE_SMELL' }); + } + + getUrlForBugsOrVulnerabilities(type: string, inNewCodePeriod: boolean) { + const { condition } = this.props; + const threshold = condition.level === 'ERROR' ? condition.error : condition.warning; + + return this.getIssuesUrl(inNewCodePeriod, { + types: type, + severities: RATING_TO_SEVERITIES_MAPPING[Number(threshold) - 1], + }); + } + + wrapWithLink(children: React.ReactNode) { + const { branchLike, component, condition } = this.props; + + const metricKey = condition.measure.metric.key; + + const METRICS_TO_URL_MAPPING: Dict<() => Path> = { + [MetricKey.reliability_rating]: () => + this.getUrlForBugsOrVulnerabilities(IssueType.Bug, false), + [MetricKey.new_reliability_rating]: () => + this.getUrlForBugsOrVulnerabilities(IssueType.Bug, true), + [MetricKey.security_rating]: () => + this.getUrlForBugsOrVulnerabilities(IssueType.Vulnerability, false), + [MetricKey.new_security_rating]: () => + this.getUrlForBugsOrVulnerabilities(IssueType.Vulnerability, true), + [MetricKey.sqale_rating]: () => this.getUrlForCodeSmells(false), + [MetricKey.new_maintainability_rating]: () => this.getUrlForCodeSmells(true), + [MetricKey.security_hotspots_reviewed]: () => this.getUrlForSecurityHotspot(false), + [MetricKey.new_security_hotspots_reviewed]: () => this.getUrlForSecurityHotspot(true), + }; + + if (METRICS_TO_URL_MAPPING[metricKey]) { + return {children}; + } + + const url = isIssueMeasure(condition.measure.metric.key) + ? getComponentIssuesUrl(component.key, { + ...propsToIssueParams(condition.measure.metric.key, condition.period != null), + ...getBranchLikeQuery(branchLike), + }) + : getComponentDrilldownUrl({ + componentKey: component.key, + metric: condition.measure.metric.key, + branchLike, + listView: true, + }); + + return {children}; + } + + getPrimaryText = () => { + const { condition } = this.props; + const { measure } = condition; + const { metric } = measure; + const isDiff = isDiffMetric(metric.key); + + const subText = + !isDiff && condition.period != null + ? `${localizeMetric(metric.key)} ${translate('quality_gates.conditions.new_code')}` + : localizeMetric(metric.key); + + if (metric.type !== MetricType.Rating) { + const actual = (condition.period ? measure.period?.value : measure.value) as string; + const formattedValue = formatMeasure(actual, metric.type, { + decimal: 2, + omitExtraDecimalZeros: metric.type === MetricType.Percent, + }); + return `${formattedValue} ${subText}`; + } + + return subText; + }; + + render() { + const { condition } = this.props; + const { measure } = condition; + const { metric } = measure; + + const threshold = (condition.level === 'ERROR' ? condition.error : condition.warning) as string; + const actual = (condition.period ? measure.period?.value : measure.value) as string; + + const operator = getOperatorLabel(condition.op, metric); + + return this.wrapWithLink( +
+ +
+
+ + + {this.getPrimaryText()} + +
+ +
+
, + ); + } +} 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 new file mode 100644 index 00000000000..de461bfb406 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateConditions.tsx @@ -0,0 +1,103 @@ +/* + * 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 { BasicSeparator, Link } from 'design-system'; +import { sortBy } from 'lodash'; +import * as React from 'react'; +import { translate } from '../../../helpers/l10n'; +import { BranchLike } from '../../../types/branch-like'; +import { MetricKey } from '../../../types/metrics'; +import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates'; +import { Component } from '../../../types/types'; +import QualityGateCondition from './QualityGateCondition'; +import QualityGateSimplifiedCondition from './QualityGateSimplifiedCondition'; + +const LEVEL_ORDER = ['ERROR', 'WARN']; + +export interface QualityGateConditionsProps { + branchLike?: BranchLike; + component: Pick; + collapsible?: boolean; + failedConditions: QualityGateStatusConditionEnhanced[]; + isBuiltInQualityGate?: boolean; +} + +const MAX_CONDITIONS = 5; + +export function QualityGateConditions(props: Readonly) { + const { branchLike, collapsible, component, failedConditions, isBuiltInQualityGate } = props; + const [collapsed, toggleCollapsed] = React.useState(Boolean(collapsible)); + + const handleToggleCollapsed = React.useCallback(() => toggleCollapsed(!collapsed), [collapsed]); + + const isSimplifiedCondition = React.useCallback( + (condition: QualityGateStatusConditionEnhanced) => { + const { metric } = condition.measure; + return metric.key === MetricKey.new_violations && isBuiltInQualityGate; + }, + [isBuiltInQualityGate], + ); + + const sortedConditions = sortBy(failedConditions, (condition) => + LEVEL_ORDER.indexOf(condition.level), + ); + + let renderConditions; + let renderCollapsed; + + if (collapsed && sortedConditions.length > MAX_CONDITIONS) { + renderConditions = sortedConditions.slice(0, MAX_CONDITIONS); + renderCollapsed = true; + } else { + renderConditions = sortedConditions; + renderCollapsed = false; + } + + return ( +
    + {renderConditions.map((condition) => ( +
    + {isSimplifiedCondition(condition) ? ( + + ) : ( + + )} + +
    + ))} + {renderCollapsed && ( +
  • + + {translate('show_more')} + +
  • + )} +
+ ); +} + +export default React.memo(QualityGateConditions); 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 0118908120d..d752b0f4b23 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 @@ -23,12 +23,12 @@ import { ComponentQualifier, isApplication } from '../../../types/component'; import { QualityGateStatus } from '../../../types/quality-gates'; import { CaycStatus, Component, QualityGate } from '../../../types/types'; import IgnoredConditionWarning from '../components/IgnoredConditionWarning'; -import QualityGateStatusHeader from '../components/QualityGateStatusHeader'; -import QualityGateStatusPassedView from '../components/QualityGateStatusPassedView'; -import { QualityGateStatusTitle } from '../components/QualityGateStatusTitle'; import ApplicationNonCaycProjectWarning from './ApplicationNonCaycProjectWarning'; import CleanAsYouCodeWarning from './CleanAsYouCodeWarning'; import QualityGatePanelSection from './QualityGatePanelSection'; +import QualityGateStatusHeader from './QualityGateStatusHeader'; +import QualityGateStatusPassedView from './QualityGateStatusPassedView'; +import { QualityGateStatusTitle } from './QualityGateStatusTitle'; export interface QualityGatePanelProps { component: Pick; 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 96a9057782c..d591c286381 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 @@ -27,8 +27,8 @@ import { QualityGateStatusConditionEnhanced, } from '../../../types/quality-gates'; import { QualityGate } from '../../../types/types'; -import QualityGateConditions from '../components/QualityGateConditions'; import ZeroNewIssuesSimplificationGuide from '../components/ZeroNewIssuesSimplificationGuide'; +import QualityGateConditions from './QualityGateConditions'; export interface QualityGatePanelSectionProps { branchLike?: BranchLike; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateSimplifiedCondition.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateSimplifiedCondition.tsx new file mode 100644 index 00000000000..d4172a7110a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateSimplifiedCondition.tsx @@ -0,0 +1,88 @@ +/* + * 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 { Highlight, LinkBox } from 'design-system'; +import * as React from 'react'; +import { propsToIssueParams } from '../../../components/shared/utils'; +import { getBranchLikeQuery } from '../../../helpers/branch-like'; +import { translate } from '../../../helpers/l10n'; +import { formatMeasure, isDiffMetric, localizeMetric } from '../../../helpers/measures'; +import { getComponentIssuesUrl } from '../../../helpers/urls'; +import { BranchLike } from '../../../types/branch-like'; +import { MetricKey, MetricType } from '../../../types/metrics'; +import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates'; +import { Component } from '../../../types/types'; + +interface Props { + branchLike?: BranchLike; + component: Pick; + condition: QualityGateStatusConditionEnhanced; +} + +export default function QualityGateSimplifiedCondition({ + branchLike, + component, + condition, +}: Readonly) { + const getPrimaryText = () => { + const { measure } = condition; + const { metric } = measure; + const isDiff = isDiffMetric(metric.key); + + const subText = + !isDiff && condition.period != null + ? `${localizeMetric(metric.key)} ${translate('quality_gates.conditions.new_code')}` + : localizeMetric(metric.key); + + return subText; + }; + + const { measure } = condition; + const { metric } = measure; + + const value = (condition.period ? measure.period?.value : measure.value) as string; + + const formattedValue = formatMeasure(value, MetricType.ShortInteger, { + decimals: 0, + omitExtraDecimalZeros: metric.type === MetricType.Percent, + }); + + return ( + +
+ {formattedValue} + + {getPrimaryText()} + +
+
+ ); +} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusHeader.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusHeader.tsx new file mode 100644 index 00000000000..c4eee2fd3ae --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusHeader.tsx @@ -0,0 +1,54 @@ +/* + * 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 { QualityGateIndicator, TextError } from 'design-system'; +import React from 'react'; +import { useIntl } from 'react-intl'; +import { translate } from '../../../helpers/l10n'; +import { Status } from '../../../types/types'; + +interface Props { + status: Status; + failedConditionCount: number; +} + +export default function QualityGateStatusHeader(props: Props) { + const { status, failedConditionCount } = props; + const intl = useIntl(); + + return ( +
+ +
+ {translate('metric.level', status)} + {failedConditionCount > 0 && ( + {failedConditionCount}, + }, + )} + /> + )} +
+
+ ); +} 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 new file mode 100644 index 00000000000..2a60ea651cb --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusPassedView.tsx @@ -0,0 +1,31 @@ +/* + * 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/QualityGateStatusTitle.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusTitle.tsx new file mode 100644 index 00000000000..14e985cb86b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusTitle.tsx @@ -0,0 +1,42 @@ +/* + * 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 { BasicSeparator, HelperHintIcon, PageTitle } from 'design-system'; +import React from 'react'; +import HelpTooltip from '../../../components/controls/HelpTooltip'; +import { translate } from '../../../helpers/l10n'; + +export function QualityGateStatusTitle() { + return ( + <> +
+
+ + {translate('overview.quality_gate.help')}
} + > + + +
+ + + + ); +} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/SonarLintPromotion.tsx b/server/sonar-web/src/main/js/apps/overview/branches/SonarLintPromotion.tsx new file mode 100644 index 00000000000..dbcf3ecb876 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/SonarLintPromotion.tsx @@ -0,0 +1,86 @@ +/* + * 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 { Card, DiscreetLink } from 'design-system'; +import * as React from 'react'; +import { FormattedMessage } from 'react-intl'; +import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext'; +import SonarLintIcon from '../../../components/icons/SonarLintIcon'; +import { translate } from '../../../helpers/l10n'; +import { MetricKey } from '../../../types/metrics'; +import { QualityGateStatusCondition } from '../../../types/quality-gates'; +import { CurrentUser } from '../../../types/users'; + +export interface SonarLintPromotionProps { + currentUser: CurrentUser; + qgConditions?: QualityGateStatusCondition[]; +} + +const CONDITIONS_TO_SHOW = [ + MetricKey.new_blocker_violations, + MetricKey.new_critical_violations, + MetricKey.new_info_violations, + MetricKey.new_violations, + MetricKey.new_major_violations, + MetricKey.new_minor_violations, + MetricKey.new_code_smells, + MetricKey.new_bugs, + MetricKey.new_vulnerabilities, + MetricKey.new_security_rating, + MetricKey.new_maintainability_rating, + MetricKey.new_reliability_rating, +]; + +export function SonarLintPromotion({ currentUser, qgConditions }: SonarLintPromotionProps) { + const showMessage = qgConditions?.some( + (qgCondition) => + CONDITIONS_TO_SHOW.includes(qgCondition.metric) && qgCondition.level === 'ERROR', + ); + if (!showMessage || currentUser.usingSonarLintConnectedMode) { + return null; + } + return ( + + + + SonarLint + + + + + + ), + }} + /> + + ); +} + +export default withCurrentUserContext(SonarLintPromotion); diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/DebtValue-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/DebtValue-test.tsx deleted file mode 100644 index e5286108b20..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/DebtValue-test.tsx +++ /dev/null @@ -1,72 +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 { screen } from '@testing-library/react'; -import * as React from 'react'; -import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; -import { mockComponent } from '../../../../helpers/mocks/component'; -import { mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks'; -import { renderComponent } from '../../../../helpers/testReactTestingUtils'; -import { MetricKey } from '../../../../types/metrics'; -import { DebtValue, DebtValueProps } from '../DebtValue'; - -it('should render correctly', () => { - renderDebtValue(); - - expect( - screen.getByLabelText( - 'overview.see_more_details_on_x_of_y.work_duration.x_minutes.1.sqale_index', - ), - ).toBeInTheDocument(); - - expect(screen.getByText('sqale_index')).toBeInTheDocument(); -}); - -it('should render diff metric correctly', () => { - renderDebtValue({ useDiffMetric: true }); - - expect( - screen.getByLabelText( - 'overview.see_more_details_on_x_of_y.work_duration.x_minutes.1.new_technical_debt', - ), - ).toBeInTheDocument(); - - expect(screen.getByText('new_technical_debt')).toBeInTheDocument(); -}); - -it('should handle missing measure', () => { - renderDebtValue({ measures: [] }); - - expect(screen.getByLabelText('no_data')).toBeInTheDocument(); - expect(screen.getByText('metric.sqale_index.name')).toBeInTheDocument(); -}); - -function renderDebtValue(props: Partial = {}) { - return renderComponent( - , - ); -} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateCondition-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateCondition-test.tsx new file mode 100644 index 00000000000..85d384bbae9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateCondition-test.tsx @@ -0,0 +1,97 @@ +/* + * 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 { screen } from '@testing-library/react'; +import * as React from 'react'; +import { mockBranch } from '../../../../helpers/mocks/branch-like'; +import { mockQualityGateStatusConditionEnhanced } from '../../../../helpers/mocks/quality-gates'; +import { mockMetric } from '../../../../helpers/testMocks'; +import { renderComponent } from '../../../../helpers/testReactTestingUtils'; +import { MetricKey, MetricType } from '../../../../types/metrics'; +import { QualityGateStatusConditionEnhanced } from '../../../../types/quality-gates'; +import QualityGateCondition from '../QualityGateCondition'; + +it.each([ + [quickMock(MetricKey.reliability_rating)], + [quickMock(MetricKey.security_rating)], + [quickMock(MetricKey.sqale_rating)], + [quickMock(MetricKey.new_reliability_rating, 'RATING', true)], + [quickMock(MetricKey.new_security_rating, 'RATING', true)], + [quickMock(MetricKey.new_maintainability_rating, 'RATING', true)], + [quickMock(MetricKey.security_hotspots_reviewed)], + [quickMock(MetricKey.new_security_hotspots_reviewed, 'RATING', true)], +])('should render correclty', async (condition) => { + renderQualityGateCondition({ condition }); + expect( + await screen.findByText(`metric.${condition.measure.metric.name}.name`), + ).toBeInTheDocument(); + + expect( + await screen.findByText(`quality_gates.operator.${condition.op}`, { exact: false }), + ).toBeInTheDocument(); + // if (condition.measure.metric.type === 'RATING') { + // expect(await screen.findByText('.rating', { exact: false })).toBeInTheDocument(); + // } +}); + +it('should show the count when metric is not rating', async () => { + renderQualityGateCondition({ condition: quickMock(MetricKey.open_issues, MetricType.Integer) }); + expect(await screen.findByText('3 metric.open_issues.name')).toBeInTheDocument(); +}); + +it('should work with branch', async () => { + const condition = quickMock(MetricKey.new_maintainability_rating); + renderQualityGateCondition({ branchLike: mockBranch(), condition }); + + expect(await screen.findByText('metric.new_maintainability_rating.name')).toBeInTheDocument(); + expect( + await screen.findByText('quality_gates.operator.GT.rating', { exact: false }), + ).toBeInTheDocument(); +}); + +function renderQualityGateCondition(props: Partial) { + return renderComponent( + , + ); +} + +function quickMock( + metric: MetricKey, + type = 'RATING', + addPeriod = false, +): QualityGateStatusConditionEnhanced { + return mockQualityGateStatusConditionEnhanced({ + error: '1', + measure: { + metric: mockMetric({ + key: metric, + name: metric, + type, + }), + value: '3', + ...(addPeriod ? { period: { value: '3', index: 1 } } : {}), + }, + metric, + ...(addPeriod ? { period: 1 } : {}), + }); +} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateConditions-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateConditions-test.tsx new file mode 100644 index 00000000000..46697d0246d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateConditions-test.tsx @@ -0,0 +1,72 @@ +/* + * 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 { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import * as React from 'react'; +import { mockComponent } from '../../../../helpers/mocks/component'; +import { mockQualityGateStatusConditionEnhanced } from '../../../../helpers/mocks/quality-gates'; +import { mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks'; +import { renderComponent } from '../../../../helpers/testReactTestingUtils'; +import { QualityGateStatusConditionEnhanced } from '../../../../types/quality-gates'; +import { QualityGateConditions, QualityGateConditionsProps } from '../QualityGateConditions'; + +const ALL_CONDITIONS = 10; +const HALF_CONDITIONS = 5; + +it('should render correctly', async () => { + renderQualityGateConditions(); + expect(await screen.findAllByText(/.*metric..+.name.*/)).toHaveLength(ALL_CONDITIONS); + + expect(await screen.findAllByText('quality_gates.operator', { exact: false })).toHaveLength( + ALL_CONDITIONS, + ); +}); + +it('should be collapsible', async () => { + renderQualityGateConditions({ collapsible: true }); + const user = userEvent.setup(); + + expect(await screen.findAllByText(/.*metric..+.name.*/)).toHaveLength(HALF_CONDITIONS); + expect(await screen.findAllByText('quality_gates.operator', { exact: false })).toHaveLength( + HALF_CONDITIONS, + ); + + await user.click(screen.getByRole('link', { name: 'show_more' })); + + expect(await screen.findAllByText(/.*metric..+.name.*/)).toHaveLength(ALL_CONDITIONS); + expect(await screen.findAllByText('quality_gates.operator', { exact: false })).toHaveLength( + ALL_CONDITIONS, + ); +}); + +function renderQualityGateConditions(props: Partial = {}) { + const conditions: QualityGateStatusConditionEnhanced[] = []; + for (let i = ALL_CONDITIONS; i > 0; --i) { + conditions.push( + mockQualityGateStatusConditionEnhanced({ + measure: mockMeasureEnhanced({ metric: mockMetric({ key: i.toString() }) }), + }), + ); + } + + return renderComponent( + , + ); +} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateSimplifiedCondition-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateSimplifiedCondition-test.tsx new file mode 100644 index 00000000000..fffc444c8d4 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateSimplifiedCondition-test.tsx @@ -0,0 +1,67 @@ +/* + * 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 { screen } from '@testing-library/react'; +import React from 'react'; +import { mockQualityGateStatusConditionEnhanced } from '../../../../helpers/mocks/quality-gates'; +import { mockMetric } from '../../../../helpers/testMocks'; +import { renderComponent } from '../../../../helpers/testReactTestingUtils'; +import { MetricKey, MetricType } from '../../../../types/metrics'; +import { QualityGateStatusConditionEnhanced } from '../../../../types/quality-gates'; +import QualityGateCondition from '../QualityGateCondition'; +import QualityGateSimplifiedCondition from '../QualityGateSimplifiedCondition'; + +it('should show simplified condition', async () => { + renderQualityGateCondition({ + condition: quickMock(MetricKey.new_violations, MetricType.Integer), + }); + expect(await screen.findByText('metric.new_violations.name')).toBeInTheDocument(); +}); + +function renderQualityGateCondition(props: Partial) { + return renderComponent( + , + ); +} + +function quickMock( + metric: MetricKey, + type = MetricType.Rating, + addPeriod = false, + value = '3', +): QualityGateStatusConditionEnhanced { + return mockQualityGateStatusConditionEnhanced({ + error: '1', + measure: { + metric: mockMetric({ + key: metric, + name: metric, + type, + }), + value, + ...(addPeriod ? { period: { value, index: 1 } } : {}), + }, + metric, + ...(addPeriod ? { period: 1 } : {}), + }); +} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/SonarLintPromotion-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/SonarLintPromotion-test.tsx new file mode 100644 index 00000000000..25f8906e358 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/SonarLintPromotion-test.tsx @@ -0,0 +1,67 @@ +/* + * 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 { screen } from '@testing-library/react'; +import * as React from 'react'; +import { mockQualityGateStatusCondition } from '../../../../helpers/mocks/quality-gates'; +import { mockCurrentUser } from '../../../../helpers/testMocks'; +import { renderComponent } from '../../../../helpers/testReactTestingUtils'; +import { MetricKey } from '../../../../types/metrics'; +import { SonarLintPromotion, SonarLintPromotionProps } from '../SonarLintPromotion'; + +it('should render correctly', () => { + renderSonarLintPromotion(); + expect( + screen.queryByText('overview.fix_failed_conditions_with_sonarlint'), + ).not.toBeInTheDocument(); + + renderSonarLintPromotion({ currentUser: mockCurrentUser({ usingSonarLintConnectedMode: true }) }); + expect( + screen.queryByText('overview.fix_failed_conditions_with_sonarlint'), + ).not.toBeInTheDocument(); +}); + +it.each( + [ + MetricKey.new_blocker_violations, + MetricKey.new_critical_violations, + MetricKey.new_info_violations, + MetricKey.new_violations, + MetricKey.new_major_violations, + MetricKey.new_minor_violations, + MetricKey.new_code_smells, + MetricKey.new_bugs, + MetricKey.new_vulnerabilities, + MetricKey.new_security_rating, + MetricKey.new_maintainability_rating, + MetricKey.new_reliability_rating, + ].map(Array.of), +)('should show message for %s', async (metric) => { + renderSonarLintPromotion({ + qgConditions: [mockQualityGateStatusCondition({ metric: metric as MetricKey })], + }); + + expect( + await screen.findByText('overview.fix_failed_conditions_with_sonarlint'), + ).toBeInTheDocument(); +}); + +function renderSonarLintPromotion(props: Partial = {}) { + return renderComponent(); +} diff --git a/server/sonar-web/src/main/js/apps/overview/components/BranchQualityGate.tsx b/server/sonar-web/src/main/js/apps/overview/components/BranchQualityGate.tsx deleted file mode 100644 index 2cb03cfc314..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/BranchQualityGate.tsx +++ /dev/null @@ -1,86 +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 { HelperHintIcon, LightPrimary, QualityGateIndicator, TextMuted } from 'design-system'; -import React from 'react'; -import { useIntl } from 'react-intl'; -import HelpTooltip from '../../../components/controls/HelpTooltip'; -import { BranchLike } from '../../../types/branch-like'; -import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates'; -import { Component, Status } from '../../../types/types'; -import BranchQualityGateConditions from './BranchQualityGateConditions'; - -interface Props { - status: Status; - branchLike?: BranchLike; - component: Pick; - failedConditions: QualityGateStatusConditionEnhanced[]; -} - -export default function BranchQualityGate(props: Readonly) { - const { status, branchLike, component, failedConditions } = props; - - return ( - <> - - - - ); -} - -function BranchQGStatus({ status }: Readonly>) { - const intl = useIntl(); - - return ( -
- -
-
- - - - -
-
- - {intl.formatMessage({ id: `metric.level.${status}` })} - -
-
-
- ); -} diff --git a/server/sonar-web/src/main/js/apps/overview/components/BranchQualityGateConditions.tsx b/server/sonar-web/src/main/js/apps/overview/components/BranchQualityGateConditions.tsx deleted file mode 100644 index d3bfaa0bdad..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/BranchQualityGateConditions.tsx +++ /dev/null @@ -1,221 +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 styled from '@emotion/styled'; -import { Badge, ButtonSecondary, themeBorder, themeColor } from 'design-system'; -import React from 'react'; -import { useIntl } from 'react-intl'; -import { - DEFAULT_ISSUES_QUERY, - isIssueMeasure, - propsToIssueParams, -} from '../../../components/shared/utils'; -import { getBranchLikeQuery } from '../../../helpers/branch-like'; -import { getLocalizedMetricName, translate } from '../../../helpers/l10n'; -import { formatMeasure, getShortType, isDiffMetric } from '../../../helpers/measures'; -import { - getComponentDrilldownUrl, - getComponentIssuesUrl, - getComponentSecurityHotspotsUrl, -} from '../../../helpers/urls'; -import { BranchLike } from '../../../types/branch-like'; -import { IssueType } from '../../../types/issues'; -import { MetricType } from '../../../types/metrics'; -import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates'; -import { Component } from '../../../types/types'; -import { - METRICS_REPORTED_IN_OVERVIEW_CARDS, - RATING_METRICS_MAPPING, - RATING_TO_SEVERITIES_MAPPING, -} from '../utils'; - -interface Props { - branchLike?: BranchLike; - component: Pick; - failedConditions: QualityGateStatusConditionEnhanced[]; -} - -export default function BranchQualityGateConditions(props: Readonly) { - const { branchLike, component, failedConditions } = props; - - const filteredFailedConditions = failedConditions.filter( - (condition) => !METRICS_REPORTED_IN_OVERVIEW_CARDS.includes(condition.metric), - ); - - return ( -
    - {filteredFailedConditions.map((condition) => ( -
  • - -
  • - ))} -
- ); -} - -function FailedQGCondition( - props: Readonly< - Pick & { condition: QualityGateStatusConditionEnhanced } - >, -) { - const { branchLike, component, condition } = props; - const url = getQGConditionUrl(component.key, condition, branchLike); - - return ( - - - {translate('overview.measures.failed_badge')} - - - - - - ); -} - -interface FailedMetricProps { - condition: QualityGateStatusConditionEnhanced; -} - -export function FailedMetric(props: Readonly) { - const { - condition: { - measure: { metric }, - }, - } = props; - - if (metric.type === MetricType.Rating) { - return ; - } - - return ; -} - -function FailedRatingMetric({ condition }: Readonly) { - const { - error, - actual, - measure: { - metric: { type, domain }, - }, - } = condition; - const intl = useIntl(); - - return ( - <> - {intl.formatMessage( - { id: 'overview.failed_condition.x_rating_required' }, - { - rating: `${intl.formatMessage({ - id: `metric_domain.${domain}`, - })} ${intl.formatMessage({ id: 'metric.type.RATING' }).toLowerCase()}`, - value: {formatMeasure(actual, type)}, - threshold: formatMeasure(error, type), - }, - )} - - ); -} - -function FailedGeneralMetric({ condition }: Readonly) { - const { - error, - measure: { metric }, - } = condition; - const intl = useIntl(); - const measureFormattingOptions = { decimals: 2, omitExtraDecimalZeros: true }; - - return ( - <> - {intl.formatMessage( - { id: 'overview.failed_condition.x_required' }, - { - metric: ( - <> - - {formatMeasure( - condition.actual, - getShortType(metric.type), - measureFormattingOptions, - )} - - {getLocalizedMetricName(metric, true)} - - ), - threshold: ( - <> - {condition.op === 'GT' ? <>≤ : <>≥}{' '} - {formatMeasure(error, getShortType(metric.type), measureFormattingOptions)} - - ), - }, - )} - - ); -} - -function getQGConditionUrl( - componentKey: string, - condition: QualityGateStatusConditionEnhanced, - branchLike?: BranchLike, -) { - const { metric } = condition; - const sinceLeakPeriod = isDiffMetric(metric); - const ratingIssueType = RATING_METRICS_MAPPING[metric]; - - if (ratingIssueType) { - if (ratingIssueType === IssueType.SecurityHotspot) { - return getComponentSecurityHotspotsUrl(componentKey, { - ...getBranchLikeQuery(branchLike), - ...(sinceLeakPeriod ? { sinceLeakPeriod: 'true' } : {}), - }); - } - return getComponentIssuesUrl(componentKey, { - ...DEFAULT_ISSUES_QUERY, - types: ratingIssueType, - ...getBranchLikeQuery(branchLike), - ...(sinceLeakPeriod ? { sinceLeakPeriod: 'true' } : {}), - ...(ratingIssueType !== IssueType.CodeSmell - ? { severities: RATING_TO_SEVERITIES_MAPPING[Number(condition.error) - 1] } - : {}), - }); - } - - if (isIssueMeasure(condition.measure.metric.key)) { - return getComponentIssuesUrl(componentKey, { - ...propsToIssueParams(condition.measure.metric.key, condition.period != null), - ...getBranchLikeQuery(branchLike), - }); - } - - return getComponentDrilldownUrl({ - componentKey, - metric, - branchLike, - listView: true, - }); -} - -const StyledConditionButton = styled(ButtonSecondary)` - --border: ${themeBorder('default')}; -`; - -const SpanDanger = styled.span` - color: ${themeColor('danger')}; -`; diff --git a/server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx b/server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx deleted file mode 100644 index 6dd83fcd6d9..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx +++ /dev/null @@ -1,110 +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, HelperHintIcon, LightLabel } from 'design-system'; -import * as React from 'react'; -import HelpTooltip from '../../../components/controls/HelpTooltip'; -import Tooltip from '../../../components/controls/Tooltip'; -import { getLeakValue } from '../../../components/measure/utils'; -import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils'; -import { getBranchLikeQuery } from '../../../helpers/branch-like'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { findMeasure, formatMeasure, localizeMetric } from '../../../helpers/measures'; -import { getComponentIssuesUrl, getComponentSecurityHotspotsUrl } from '../../../helpers/urls'; -import { BranchLike } from '../../../types/branch-like'; -import { ComponentQualifier } from '../../../types/component'; -import { IssueType } from '../../../types/issues'; -import { MetricType } from '../../../types/metrics'; -import { Component, MeasureEnhanced } from '../../../types/types'; -import { getIssueMetricKey } from '../utils'; -import { OverviewDisabledLinkTooltip } from './OverviewDisabledLinkTooltip'; - -export interface IssueLabelProps { - branchLike?: BranchLike; - component: Component; - helpTooltip?: string; - measures: MeasureEnhanced[]; - type: IssueType; - useDiffMetric?: boolean; -} - -export function IssueLabel(props: IssueLabelProps) { - const { branchLike, component, helpTooltip, measures, type, useDiffMetric = false } = props; - const metricKey = getIssueMetricKey(type, useDiffMetric); - const measure = findMeasure(measures, metricKey); - - let value; - - if (measure) { - value = useDiffMetric ? getLeakValue(measure) : measure.value; - } - - const params = { - ...getBranchLikeQuery(branchLike), - inNewCodePeriod: useDiffMetric ? 'true' : 'false', - ...DEFAULT_ISSUES_QUERY, - types: type, - }; - - const url = - type === IssueType.SecurityHotspot - ? getComponentSecurityHotspotsUrl(component.key, params) - : getComponentIssuesUrl(component.key, params); - - const disabled = - component.qualifier === ComponentQualifier.Application && component.needIssueSync; - - const drilldownLinkProps = disabled - ? { disabled, to: '' } - : { - 'aria-label': translateWithParameters( - 'overview.see_list_of_x_y_issues', - value as string, - localizeMetric(metricKey), - ), - to: url, - }; - - return ( -
- {value === undefined ? ( - — - ) : ( - } - > - - {formatMeasure(value, MetricType.ShortInteger)} - - - )} - - {localizeMetric(metricKey)} - - {helpTooltip && ( - - - - )} -
- ); -} - -export default React.memo(IssueLabel); diff --git a/server/sonar-web/src/main/js/apps/overview/components/IssueRating.tsx b/server/sonar-web/src/main/js/apps/overview/components/IssueRating.tsx deleted file mode 100644 index f22c3052bf9..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/IssueRating.tsx +++ /dev/null @@ -1,84 +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. - */ -/* eslint-disable react/no-unused-prop-types */ - -import { DiscreetLinkBox, MetricsRatingBadge } from 'design-system'; -import * as React from 'react'; -import Tooltip from '../../../components/controls/Tooltip'; -import RatingTooltipContent from '../../../components/measure/RatingTooltipContent'; -import { getLeakValue } from '../../../components/measure/utils'; -import { translateWithParameters } from '../../../helpers/l10n'; -import { findMeasure, formatRating } from '../../../helpers/measures'; -import { getComponentDrilldownUrl } from '../../../helpers/urls'; -import { BranchLike } from '../../../types/branch-like'; -import { IssueType } from '../../../types/issues'; -import { Component, MeasureEnhanced } from '../../../types/types'; -import { getIssueRatingMetricKey } from '../utils'; - -export interface IssueRatingProps { - branchLike?: BranchLike; - component: Component; - measures: MeasureEnhanced[]; - type: IssueType; - useDiffMetric?: boolean; -} - -export function IssueRating(props: IssueRatingProps) { - const { branchLike, component, useDiffMetric = false, measures, type } = props; - const ratingKey = getIssueRatingMetricKey(type, useDiffMetric); - const measure = findMeasure(measures, ratingKey); - const rawValue = measure && (useDiffMetric ? getLeakValue(measure) : measure.value); - const value = formatRating(rawValue); - - if (!ratingKey || !measure) { - return ; - } - - return ( - }> - - {value ? ( - - - - ) : ( - - )} - - - ); -} - -export default IssueRating; - -function NoRating() { - return
–
; -} diff --git a/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.tsx b/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.tsx deleted file mode 100644 index 02963e513d1..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.tsx +++ /dev/null @@ -1,116 +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 { differenceInDays } from 'date-fns'; -import * as React from 'react'; -import { injectIntl, WrappedComponentProps } from 'react-intl'; -import Tooltip from '../../../components/controls/Tooltip'; -import DateFormatter, { longFormatterOption } from '../../../components/intl/DateFormatter'; -import DateFromNow from '../../../components/intl/DateFromNow'; -import DateTimeFormatter, { formatterOption } from '../../../components/intl/DateTimeFormatter'; -import { translateWithParameters } from '../../../helpers/l10n'; -import { getNewCodePeriodDate, getNewCodePeriodLabel } from '../../../helpers/new-code-period'; -import { NewCodeDefinitionType } from '../../../types/new-code-definition'; -import { Dict, Period } from '../../../types/types'; - -interface Props { - period: Period; -} - -const MODE_INCLUDES_TIME: Dict = { - manual_baseline: true, - SPECIFIC_ANALYSIS: true, -}; - -export class LeakPeriodLegend extends React.PureComponent { - formatDate = (date: string) => { - return this.props.intl.formatDate(date, longFormatterOption); - }; - - formatDateTime = (date: string) => { - return this.props.intl.formatTime(date, formatterOption); - }; - - render() { - const { period } = this.props; - const leakPeriodLabel = getNewCodePeriodLabel( - period, - MODE_INCLUDES_TIME[period.mode] ? this.formatDateTime : this.formatDate, - ); - if (!leakPeriodLabel) { - return null; - } - - if (period.mode === 'days' || period.mode === NewCodeDefinitionType.NumberOfDays) { - return ( -
- {translateWithParameters('overview.new_code_period_x', leakPeriodLabel)} -
- ); - } - - const leakPeriodDate = getNewCodePeriodDate(period); - if (!leakPeriodDate) { - return null; - } - - const formattedDateFunction = (formattedLeakPeriodDate: string) => ( - - {translateWithParameters( - period.mode === 'previous_analysis' - ? 'overview.previous_analysis_on_x' - : 'overview.started_on_x', - formattedLeakPeriodDate, - )} - - ); - - const tooltip = - differenceInDays(new Date(), leakPeriodDate) < 1 ? ( - {formattedDateFunction} - ) : ( - - {formattedDateFunction} - - ); - - return ( - -
- {translateWithParameters('overview.new_code_period_x', leakPeriodLabel)} -
- - {(fromNow) => ( - - {translateWithParameters( - period.mode === 'previous_analysis' - ? 'overview.previous_analysis_x' - : 'overview.started_x', - fromNow, - )} - - )} - -
-
- ); - } -} - -export default injectIntl(LeakPeriodLegend); diff --git a/server/sonar-web/src/main/js/apps/overview/components/QualityGateCondition.tsx b/server/sonar-web/src/main/js/apps/overview/components/QualityGateCondition.tsx deleted file mode 100644 index dda75f63084..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/QualityGateCondition.tsx +++ /dev/null @@ -1,182 +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 { LinkBox, TextMuted } from 'design-system'; -import * as React from 'react'; -import { Path } from 'react-router-dom'; -import IssueTypeIcon from '../../../components/icons/IssueTypeIcon'; -import MeasureIndicator from '../../../components/measure/MeasureIndicator'; -import { - DEFAULT_ISSUES_QUERY, - isIssueMeasure, - propsToIssueParams, -} from '../../../components/shared/utils'; -import { getBranchLikeQuery } from '../../../helpers/branch-like'; -import { translate } from '../../../helpers/l10n'; -import { formatMeasure, isDiffMetric, localizeMetric } from '../../../helpers/measures'; -import { getOperatorLabel } from '../../../helpers/qualityGates'; -import { - getComponentDrilldownUrl, - getComponentIssuesUrl, - getComponentSecurityHotspotsUrl, -} from '../../../helpers/urls'; -import { BranchLike } from '../../../types/branch-like'; -import { IssueType } from '../../../types/issues'; -import { MetricKey, MetricType } from '../../../types/metrics'; -import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates'; -import { Component, Dict } from '../../../types/types'; -import { RATING_TO_SEVERITIES_MAPPING } from '../utils'; - -interface Props { - branchLike?: BranchLike; - component: Pick; - condition: QualityGateStatusConditionEnhanced; -} - -export default class QualityGateCondition extends React.PureComponent { - getIssuesUrl = (inNewCodePeriod: boolean, customQuery: Dict) => { - const query: Dict = { - ...DEFAULT_ISSUES_QUERY, - ...getBranchLikeQuery(this.props.branchLike), - ...customQuery, - }; - if (inNewCodePeriod) { - Object.assign(query, { inNewCodePeriod: 'true' }); - } - return getComponentIssuesUrl(this.props.component.key, query); - }; - - getUrlForSecurityHotspot(inNewCodePeriod: boolean) { - const query: Dict = { - ...getBranchLikeQuery(this.props.branchLike), - }; - if (inNewCodePeriod) { - Object.assign(query, { inNewCodePeriod: 'true' }); - } - return getComponentSecurityHotspotsUrl(this.props.component.key, query); - } - - getUrlForCodeSmells(inNewCodePeriod: boolean) { - return this.getIssuesUrl(inNewCodePeriod, { types: 'CODE_SMELL' }); - } - - getUrlForBugsOrVulnerabilities(type: string, inNewCodePeriod: boolean) { - const { condition } = this.props; - const threshold = condition.level === 'ERROR' ? condition.error : condition.warning; - - return this.getIssuesUrl(inNewCodePeriod, { - types: type, - severities: RATING_TO_SEVERITIES_MAPPING[Number(threshold) - 1], - }); - } - - wrapWithLink(children: React.ReactNode) { - const { branchLike, component, condition } = this.props; - - const metricKey = condition.measure.metric.key; - - const METRICS_TO_URL_MAPPING: Dict<() => Path> = { - [MetricKey.reliability_rating]: () => - this.getUrlForBugsOrVulnerabilities(IssueType.Bug, false), - [MetricKey.new_reliability_rating]: () => - this.getUrlForBugsOrVulnerabilities(IssueType.Bug, true), - [MetricKey.security_rating]: () => - this.getUrlForBugsOrVulnerabilities(IssueType.Vulnerability, false), - [MetricKey.new_security_rating]: () => - this.getUrlForBugsOrVulnerabilities(IssueType.Vulnerability, true), - [MetricKey.sqale_rating]: () => this.getUrlForCodeSmells(false), - [MetricKey.new_maintainability_rating]: () => this.getUrlForCodeSmells(true), - [MetricKey.security_hotspots_reviewed]: () => this.getUrlForSecurityHotspot(false), - [MetricKey.new_security_hotspots_reviewed]: () => this.getUrlForSecurityHotspot(true), - }; - - if (METRICS_TO_URL_MAPPING[metricKey]) { - return {children}; - } - - const url = isIssueMeasure(condition.measure.metric.key) - ? getComponentIssuesUrl(component.key, { - ...propsToIssueParams(condition.measure.metric.key, condition.period != null), - ...getBranchLikeQuery(branchLike), - }) - : getComponentDrilldownUrl({ - componentKey: component.key, - metric: condition.measure.metric.key, - branchLike, - listView: true, - }); - - return {children}; - } - - getPrimaryText = () => { - const { condition } = this.props; - const { measure } = condition; - const { metric } = measure; - const isDiff = isDiffMetric(metric.key); - - const subText = - !isDiff && condition.period != null - ? `${localizeMetric(metric.key)} ${translate('quality_gates.conditions.new_code')}` - : localizeMetric(metric.key); - - if (metric.type !== MetricType.Rating) { - const actual = (condition.period ? measure.period?.value : measure.value) as string; - const formattedValue = formatMeasure(actual, metric.type, { - decimal: 2, - omitExtraDecimalZeros: metric.type === MetricType.Percent, - }); - return `${formattedValue} ${subText}`; - } - - return subText; - }; - - render() { - const { condition } = this.props; - const { measure } = condition; - const { metric } = measure; - - const threshold = (condition.level === 'ERROR' ? condition.error : condition.warning) as string; - const actual = (condition.period ? measure.period?.value : measure.value) as string; - - const operator = getOperatorLabel(condition.op, metric); - - return this.wrapWithLink( -
- -
-
- - - {this.getPrimaryText()} - -
- -
-
, - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/overview/components/QualityGateConditions.tsx b/server/sonar-web/src/main/js/apps/overview/components/QualityGateConditions.tsx deleted file mode 100644 index b74ffa88ca9..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/QualityGateConditions.tsx +++ /dev/null @@ -1,103 +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 { BasicSeparator, Link } from 'design-system'; -import { sortBy } from 'lodash'; -import * as React from 'react'; -import { translate } from '../../../helpers/l10n'; -import { BranchLike } from '../../../types/branch-like'; -import { MetricKey } from '../../../types/metrics'; -import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates'; -import { Component } from '../../../types/types'; -import QualityGateCondition from './QualityGateCondition'; -import QualityGateSimplifiedCondition from './QualityGateSimplifiedCondition'; - -const LEVEL_ORDER = ['ERROR', 'WARN']; - -export interface QualityGateConditionsProps { - branchLike?: BranchLike; - component: Pick; - collapsible?: boolean; - failedConditions: QualityGateStatusConditionEnhanced[]; - isBuiltInQualityGate?: boolean; -} - -const MAX_CONDITIONS = 5; - -export function QualityGateConditions(props: QualityGateConditionsProps) { - const { branchLike, collapsible, component, failedConditions, isBuiltInQualityGate } = props; - const [collapsed, toggleCollapsed] = React.useState(Boolean(collapsible)); - - const handleToggleCollapsed = React.useCallback(() => toggleCollapsed(!collapsed), [collapsed]); - - const isSimplifiedCondition = React.useCallback( - (condition: QualityGateStatusConditionEnhanced) => { - const { metric } = condition.measure; - return metric.key === MetricKey.new_violations && isBuiltInQualityGate; - }, - [isBuiltInQualityGate], - ); - - const sortedConditions = sortBy(failedConditions, (condition) => - LEVEL_ORDER.indexOf(condition.level), - ); - - let renderConditions; - let renderCollapsed; - - if (collapsed && sortedConditions.length > MAX_CONDITIONS) { - renderConditions = sortedConditions.slice(0, MAX_CONDITIONS); - renderCollapsed = true; - } else { - renderConditions = sortedConditions; - renderCollapsed = false; - } - - return ( -
    - {renderConditions.map((condition) => ( -
    - {isSimplifiedCondition(condition) ? ( - - ) : ( - - )} - -
    - ))} - {renderCollapsed && ( -
  • - - {translate('show_more')} - -
  • - )} -
- ); -} - -export default React.memo(QualityGateConditions); diff --git a/server/sonar-web/src/main/js/apps/overview/components/QualityGateSimplifiedCondition.tsx b/server/sonar-web/src/main/js/apps/overview/components/QualityGateSimplifiedCondition.tsx deleted file mode 100644 index d4172a7110a..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/QualityGateSimplifiedCondition.tsx +++ /dev/null @@ -1,88 +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 { Highlight, LinkBox } from 'design-system'; -import * as React from 'react'; -import { propsToIssueParams } from '../../../components/shared/utils'; -import { getBranchLikeQuery } from '../../../helpers/branch-like'; -import { translate } from '../../../helpers/l10n'; -import { formatMeasure, isDiffMetric, localizeMetric } from '../../../helpers/measures'; -import { getComponentIssuesUrl } from '../../../helpers/urls'; -import { BranchLike } from '../../../types/branch-like'; -import { MetricKey, MetricType } from '../../../types/metrics'; -import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates'; -import { Component } from '../../../types/types'; - -interface Props { - branchLike?: BranchLike; - component: Pick; - condition: QualityGateStatusConditionEnhanced; -} - -export default function QualityGateSimplifiedCondition({ - branchLike, - component, - condition, -}: Readonly) { - const getPrimaryText = () => { - const { measure } = condition; - const { metric } = measure; - const isDiff = isDiffMetric(metric.key); - - const subText = - !isDiff && condition.period != null - ? `${localizeMetric(metric.key)} ${translate('quality_gates.conditions.new_code')}` - : localizeMetric(metric.key); - - return subText; - }; - - const { measure } = condition; - const { metric } = measure; - - const value = (condition.period ? measure.period?.value : measure.value) as string; - - const formattedValue = formatMeasure(value, MetricType.ShortInteger, { - decimals: 0, - omitExtraDecimalZeros: metric.type === MetricType.Percent, - }); - - return ( - -
- {formattedValue} - - {getPrimaryText()} - -
-
- ); -} diff --git a/server/sonar-web/src/main/js/apps/overview/components/QualityGateStatusHeader.tsx b/server/sonar-web/src/main/js/apps/overview/components/QualityGateStatusHeader.tsx deleted file mode 100644 index c4eee2fd3ae..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/QualityGateStatusHeader.tsx +++ /dev/null @@ -1,54 +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 { QualityGateIndicator, TextError } from 'design-system'; -import React from 'react'; -import { useIntl } from 'react-intl'; -import { translate } from '../../../helpers/l10n'; -import { Status } from '../../../types/types'; - -interface Props { - status: Status; - failedConditionCount: number; -} - -export default function QualityGateStatusHeader(props: Props) { - const { status, failedConditionCount } = props; - const intl = useIntl(); - - return ( -
- -
- {translate('metric.level', status)} - {failedConditionCount > 0 && ( - {failedConditionCount}, - }, - )} - /> - )} -
-
- ); -} diff --git a/server/sonar-web/src/main/js/apps/overview/components/QualityGateStatusPassedView.tsx b/server/sonar-web/src/main/js/apps/overview/components/QualityGateStatusPassedView.tsx deleted file mode 100644 index 2a60ea651cb..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/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/components/QualityGateStatusTitle.tsx b/server/sonar-web/src/main/js/apps/overview/components/QualityGateStatusTitle.tsx deleted file mode 100644 index 14e985cb86b..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/QualityGateStatusTitle.tsx +++ /dev/null @@ -1,42 +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 { BasicSeparator, HelperHintIcon, PageTitle } from 'design-system'; -import React from 'react'; -import HelpTooltip from '../../../components/controls/HelpTooltip'; -import { translate } from '../../../helpers/l10n'; - -export function QualityGateStatusTitle() { - return ( - <> -
-
- - {translate('overview.quality_gate.help')}
} - > - - -
- - - - ); -} diff --git a/server/sonar-web/src/main/js/apps/overview/components/SonarLintPromotion.tsx b/server/sonar-web/src/main/js/apps/overview/components/SonarLintPromotion.tsx deleted file mode 100644 index dbcf3ecb876..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/SonarLintPromotion.tsx +++ /dev/null @@ -1,86 +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 { Card, DiscreetLink } from 'design-system'; -import * as React from 'react'; -import { FormattedMessage } from 'react-intl'; -import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext'; -import SonarLintIcon from '../../../components/icons/SonarLintIcon'; -import { translate } from '../../../helpers/l10n'; -import { MetricKey } from '../../../types/metrics'; -import { QualityGateStatusCondition } from '../../../types/quality-gates'; -import { CurrentUser } from '../../../types/users'; - -export interface SonarLintPromotionProps { - currentUser: CurrentUser; - qgConditions?: QualityGateStatusCondition[]; -} - -const CONDITIONS_TO_SHOW = [ - MetricKey.new_blocker_violations, - MetricKey.new_critical_violations, - MetricKey.new_info_violations, - MetricKey.new_violations, - MetricKey.new_major_violations, - MetricKey.new_minor_violations, - MetricKey.new_code_smells, - MetricKey.new_bugs, - MetricKey.new_vulnerabilities, - MetricKey.new_security_rating, - MetricKey.new_maintainability_rating, - MetricKey.new_reliability_rating, -]; - -export function SonarLintPromotion({ currentUser, qgConditions }: SonarLintPromotionProps) { - const showMessage = qgConditions?.some( - (qgCondition) => - CONDITIONS_TO_SHOW.includes(qgCondition.metric) && qgCondition.level === 'ERROR', - ); - if (!showMessage || currentUser.usingSonarLintConnectedMode) { - return null; - } - return ( - - - - SonarLint - - - - - - ), - }} - /> - - ); -} - -export default withCurrentUserContext(SonarLintPromotion); diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/BranchQualityGate-it.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/BranchQualityGate-it.tsx deleted file mode 100644 index a841f4d4436..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/BranchQualityGate-it.tsx +++ /dev/null @@ -1,148 +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 { mockPullRequest } from '../../../../helpers/mocks/branch-like'; -import { mockComponent } from '../../../../helpers/mocks/component'; -import { mockQualityGateStatusConditionEnhanced } from '../../../../helpers/mocks/quality-gates'; -import { mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks'; -import { renderComponent } from '../../../../helpers/testReactTestingUtils'; -import { byLabelText, byRole } from '../../../../helpers/testSelector'; -import { MetricKey, MetricType } from '../../../../types/metrics'; -import { FCProps } from '../../../../types/misc'; -import { Status } from '../../utils'; -import BranchQualityGate from '../BranchQualityGate'; - -it('renders failed QG', () => { - renderBranchQualityGate(); - - // Maintainability rating condition - const maintainabilityRatingLink = byRole('link', { - name: 'overview.measures.failed_badge overview.failed_condition.x_rating_requiredmetric_domain.Maintainability metric.type.rating E A', - }).get(); - expect(maintainabilityRatingLink).toBeInTheDocument(); - expect(maintainabilityRatingLink).toHaveAttribute( - 'href', - '/project/issues?issueStatuses=OPEN%2CCONFIRMED&types=CODE_SMELL&pullRequest=1001&sinceLeakPeriod=true&id=my-project', - ); - - // Security Hotspots rating condition - const securityHotspotsRatingLink = byRole('link', { - name: 'overview.measures.failed_badge overview.failed_condition.x_rating_requiredmetric_domain.Security Review metric.type.rating E A', - }).get(); - expect(securityHotspotsRatingLink).toBeInTheDocument(); - expect(securityHotspotsRatingLink).toHaveAttribute( - 'href', - '/security_hotspots?id=my-project&pullRequest=1001', - ); - - // New code smells - const codeSmellsLink = byRole('link', { - name: 'overview.measures.failed_badge overview.failed_condition.x_required 5 Code Smells≤ 1', - }).get(); - expect(codeSmellsLink).toBeInTheDocument(); - expect(codeSmellsLink).toHaveAttribute( - 'href', - '/project/issues?issueStatuses=OPEN%2CCONFIRMED&types=CODE_SMELL&pullRequest=1001&id=my-project', - ); - - // Conditions to cover - const conditionToCoverLink = byRole('link', { - name: 'overview.measures.failed_badge overview.failed_condition.x_required 5 Conditions to cover≥ 10', - }).get(); - expect(conditionToCoverLink).toBeInTheDocument(); - expect(conditionToCoverLink).toHaveAttribute( - 'href', - '/component_measures?id=my-project&metric=conditions_to_cover&pullRequest=1001&view=list', - ); - - expect(byLabelText('overview.quality_gate_x.overview.gate.ERROR').get()).toBeInTheDocument(); -}); - -it('renders passed QG', () => { - renderBranchQualityGate({ failedConditions: [], status: Status.OK }); - - expect(byLabelText('overview.quality_gate_x.overview.gate.OK').get()).toBeInTheDocument(); - expect(byRole('link').query()).not.toBeInTheDocument(); -}); - -function renderBranchQualityGate(props: Partial> = {}) { - return renderComponent( - , - ); -} diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueLabel-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueLabel-test.tsx deleted file mode 100644 index fda48d8b007..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueLabel-test.tsx +++ /dev/null @@ -1,108 +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 { screen } from '@testing-library/react'; -import * as React from 'react'; -import { mockPullRequest } from '../../../../helpers/mocks/branch-like'; -import { mockComponent } from '../../../../helpers/mocks/component'; -import { mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks'; -import { renderComponent } from '../../../../helpers/testReactTestingUtils'; -import { ComponentQualifier } from '../../../../types/component'; -import { IssueType } from '../../../../types/issues'; -import { MetricKey } from '../../../../types/metrics'; -import { IssueLabel, IssueLabelProps } from '../IssueLabel'; - -it('should render correctly for bugs', async () => { - const measures = [ - mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.bugs }) }), - mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_bugs }) }), - ]; - - const rtl = renderIssueLabel({ measures }); - expect( - await screen.findByRole('link', { - name: 'overview.see_list_of_x_y_issues.1.0.metric.bugs.name', - }), - ).toBeInTheDocument(); - - rtl.unmount(); - - renderIssueLabel({ measures, useDiffMetric: true }); - - expect( - await screen.findByRole('link', { - name: 'overview.see_list_of_x_y_issues.1.0.metric.new_bugs.name', - }), - ).toBeInTheDocument(); -}); - -it('should render correctly for hotspots with tooltip', async () => { - const helpTooltip = 'tooltip text'; - const type = IssueType.SecurityHotspot; - const measures = [ - mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.security_hotspots }) }), - mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_security_hotspots }) }), - ]; - - renderIssueLabel({ - helpTooltip, - measures, - type, - }); - - expect( - await screen.findByRole('link', { - name: 'overview.see_list_of_x_y_issues.1.0.metric.security_hotspots.name', - }), - ).toBeInTheDocument(); - - expect(screen.getByText('tooltip text')).toBeInTheDocument(); -}); - -it('should render correctly for a re-indexing Application', () => { - const type = IssueType.SecurityHotspot; - const measures = [ - mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.security_hotspots }) }), - mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_security_hotspots }) }), - ]; - - renderIssueLabel({ - component: mockComponent({ needIssueSync: true, qualifier: ComponentQualifier.Application }), - measures, - type, - }); - - expect( - screen.queryByRole('link', { - name: 'overview.see_list_of_x_y_issues.1.0.metric.security_hotspots.name', - }), - ).not.toBeInTheDocument(); -}); - -function renderIssueLabel(props: Partial = {}) { - return renderComponent( - , - ); -} diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueRating-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueRating-test.tsx deleted file mode 100644 index d2d2f5ba9d1..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueRating-test.tsx +++ /dev/null @@ -1,60 +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 { screen } from '@testing-library/react'; -import * as React from 'react'; -import { mockPullRequest } from '../../../../helpers/mocks/branch-like'; -import { mockComponent } from '../../../../helpers/mocks/component'; -import { mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks'; -import { renderComponent } from '../../../../helpers/testReactTestingUtils'; -import { IssueType } from '../../../../types/issues'; -import { MetricKey } from '../../../../types/metrics'; -import { IssueRating, IssueRatingProps } from '../IssueRating'; - -it('should render correctly for vulnerabilities', async () => { - renderIssueRating({ type: IssueType.Vulnerability, useDiffMetric: true }); - expect(await screen.findByLabelText('metric.has_rating_X.A')).toBeInTheDocument(); - expect(await screen.findByText('metric.security_rating.tooltip.A')).toBeInTheDocument(); -}); - -it('should render correctly if no values are present', async () => { - renderIssueRating({ - measures: [mockMeasureEnhanced({ metric: mockMetric({ key: 'NONE' }) })], - }); - expect(await screen.findByText('–')).toBeInTheDocument(); -}); - -function renderIssueRating(props: Partial = {}) { - return renderComponent( - , - ); -} diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.tsx deleted file mode 100644 index 0d322aa8fb4..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.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 { screen } from '@testing-library/react'; -import { differenceInDays } from 'date-fns'; -import * as React from 'react'; -import { IntlShape } from 'react-intl'; -import { renderComponent } from '../../../../helpers/testReactTestingUtils'; -import { Period } from '../../../../types/types'; -import { LeakPeriodLegend } from '../LeakPeriodLegend'; - -jest.mock('date-fns', () => { - const actual = jest.requireActual('date-fns'); - return { - ...actual, - differenceInDays: jest.fn().mockReturnValue(10), - differenceInYears: jest.fn().mockReturnValue(-9), - }; -}); - -it('10 days', async () => { - renderLeakPeriodLegend({ mode: 'days', parameter: '10' }); - - expect( - await screen.findByText('overview.new_code_period_x.overview.period.days.10'), - ).toBeInTheDocument(); -}); - -it('date', async () => { - renderLeakPeriodLegend({ mode: 'date', parameter: '2013-01-01' }); - - expect( - await screen.findByText('overview.new_code_period_x.overview.period.date.formatted.2013-01-01'), - ).toBeInTheDocument(); - expect(await screen.findByText('overview.started_x.9 years ago')).toBeInTheDocument(); - expect(await screen.findByText(/overview\.started_on_x\..*/)).toBeInTheDocument(); -}); - -it('version', async () => { - renderLeakPeriodLegend({ mode: 'version', parameter: '0.1' }); - - expect( - await screen.findByText('overview.new_code_period_x.overview.period.version.0.1'), - ).toBeInTheDocument(); - expect(await screen.findByText(/overview\.started_x\..*/)).toBeInTheDocument(); - expect(await screen.findByText(/overview\.started_on_x\..*/)).toBeInTheDocument(); -}); - -it('previous_version', async () => { - renderLeakPeriodLegend({ mode: 'previous_version' }); - - expect( - await screen.findByText( - 'overview.new_code_period_x.overview.period.previous_version_only_date', - ), - ).toBeInTheDocument(); - expect(await screen.findByText(/overview\.started_x\..*/)).toBeInTheDocument(); - expect(await screen.findByText(/overview\.started_on_x\..*/)).toBeInTheDocument(); -}); - -it('previous_analysis', async () => { - renderLeakPeriodLegend({ mode: 'previous_analysis' }); - - expect( - await screen.findByText('overview.new_code_period_x.overview.period.previous_analysis.'), - ).toBeInTheDocument(); - expect(await screen.findByText(/overview\.previous_analysis_x\..*/)).toBeInTheDocument(); - expect(await screen.findByText(/overview\.previous_analysis_x\..*/)).toBeInTheDocument(); -}); - -it('manual_baseline', async () => { - const rtl = renderLeakPeriodLegend({ mode: 'manual_baseline' }); - - expect( - await screen.findByText( - /overview\.new_code_period_x\.overview\.period\.manual_baseline\.formattedTime\..*/, - ), - ).toBeInTheDocument(); - expect(await screen.findByText(/overview\.started_x\..*/)).toBeInTheDocument(); - expect(await screen.findByText(/overview\.started_on_x\..*/)).toBeInTheDocument(); - - rtl.unmount(); - renderLeakPeriodLegend({ mode: 'manual_baseline', parameter: '1.1.2' }); - - expect( - await screen.findByText('overview.new_code_period_x.overview.period.manual_baseline.1.1.2'), - ).toBeInTheDocument(); - expect( - await screen.findByText('overview.new_code_period_x.overview.period.manual_baseline.1.1.2'), - ).toBeInTheDocument(); -}); - -it('should render a more precise date', async () => { - (differenceInDays as jest.Mock).mockReturnValueOnce(0); - - renderLeakPeriodLegend({ date: '2018-08-17T00:00:00+0200', mode: 'previous_version' }); - - expect( - await screen.findByText( - 'overview.new_code_period_x.overview.period.previous_version_only_date', - ), - ).toBeInTheDocument(); - expect(await screen.findByText(/overview\.started_x\..*/)).toBeInTheDocument(); - expect(await screen.findByText(/overview\.started_on_x\..*/)).toBeInTheDocument(); -}); - -function renderLeakPeriodLegend(period: Partial = {}) { - return renderComponent( - 'formatted.' + date, - formatTime: (date: string) => 'formattedTime.' + date, - } as IntlShape - } - period={{ - date: '2013-09-22T00:00:00+0200', - index: 0, - mode: 'version', - ...period, - }} - />, - ); -} diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/QualityGateCondition-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/QualityGateCondition-test.tsx deleted file mode 100644 index 85d384bbae9..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/QualityGateCondition-test.tsx +++ /dev/null @@ -1,97 +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 { screen } from '@testing-library/react'; -import * as React from 'react'; -import { mockBranch } from '../../../../helpers/mocks/branch-like'; -import { mockQualityGateStatusConditionEnhanced } from '../../../../helpers/mocks/quality-gates'; -import { mockMetric } from '../../../../helpers/testMocks'; -import { renderComponent } from '../../../../helpers/testReactTestingUtils'; -import { MetricKey, MetricType } from '../../../../types/metrics'; -import { QualityGateStatusConditionEnhanced } from '../../../../types/quality-gates'; -import QualityGateCondition from '../QualityGateCondition'; - -it.each([ - [quickMock(MetricKey.reliability_rating)], - [quickMock(MetricKey.security_rating)], - [quickMock(MetricKey.sqale_rating)], - [quickMock(MetricKey.new_reliability_rating, 'RATING', true)], - [quickMock(MetricKey.new_security_rating, 'RATING', true)], - [quickMock(MetricKey.new_maintainability_rating, 'RATING', true)], - [quickMock(MetricKey.security_hotspots_reviewed)], - [quickMock(MetricKey.new_security_hotspots_reviewed, 'RATING', true)], -])('should render correclty', async (condition) => { - renderQualityGateCondition({ condition }); - expect( - await screen.findByText(`metric.${condition.measure.metric.name}.name`), - ).toBeInTheDocument(); - - expect( - await screen.findByText(`quality_gates.operator.${condition.op}`, { exact: false }), - ).toBeInTheDocument(); - // if (condition.measure.metric.type === 'RATING') { - // expect(await screen.findByText('.rating', { exact: false })).toBeInTheDocument(); - // } -}); - -it('should show the count when metric is not rating', async () => { - renderQualityGateCondition({ condition: quickMock(MetricKey.open_issues, MetricType.Integer) }); - expect(await screen.findByText('3 metric.open_issues.name')).toBeInTheDocument(); -}); - -it('should work with branch', async () => { - const condition = quickMock(MetricKey.new_maintainability_rating); - renderQualityGateCondition({ branchLike: mockBranch(), condition }); - - expect(await screen.findByText('metric.new_maintainability_rating.name')).toBeInTheDocument(); - expect( - await screen.findByText('quality_gates.operator.GT.rating', { exact: false }), - ).toBeInTheDocument(); -}); - -function renderQualityGateCondition(props: Partial) { - return renderComponent( - , - ); -} - -function quickMock( - metric: MetricKey, - type = 'RATING', - addPeriod = false, -): QualityGateStatusConditionEnhanced { - return mockQualityGateStatusConditionEnhanced({ - error: '1', - measure: { - metric: mockMetric({ - key: metric, - name: metric, - type, - }), - value: '3', - ...(addPeriod ? { period: { value: '3', index: 1 } } : {}), - }, - metric, - ...(addPeriod ? { period: 1 } : {}), - }); -} diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/QualityGateConditions-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/QualityGateConditions-test.tsx deleted file mode 100644 index 46697d0246d..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/QualityGateConditions-test.tsx +++ /dev/null @@ -1,72 +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 { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import * as React from 'react'; -import { mockComponent } from '../../../../helpers/mocks/component'; -import { mockQualityGateStatusConditionEnhanced } from '../../../../helpers/mocks/quality-gates'; -import { mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks'; -import { renderComponent } from '../../../../helpers/testReactTestingUtils'; -import { QualityGateStatusConditionEnhanced } from '../../../../types/quality-gates'; -import { QualityGateConditions, QualityGateConditionsProps } from '../QualityGateConditions'; - -const ALL_CONDITIONS = 10; -const HALF_CONDITIONS = 5; - -it('should render correctly', async () => { - renderQualityGateConditions(); - expect(await screen.findAllByText(/.*metric..+.name.*/)).toHaveLength(ALL_CONDITIONS); - - expect(await screen.findAllByText('quality_gates.operator', { exact: false })).toHaveLength( - ALL_CONDITIONS, - ); -}); - -it('should be collapsible', async () => { - renderQualityGateConditions({ collapsible: true }); - const user = userEvent.setup(); - - expect(await screen.findAllByText(/.*metric..+.name.*/)).toHaveLength(HALF_CONDITIONS); - expect(await screen.findAllByText('quality_gates.operator', { exact: false })).toHaveLength( - HALF_CONDITIONS, - ); - - await user.click(screen.getByRole('link', { name: 'show_more' })); - - expect(await screen.findAllByText(/.*metric..+.name.*/)).toHaveLength(ALL_CONDITIONS); - expect(await screen.findAllByText('quality_gates.operator', { exact: false })).toHaveLength( - ALL_CONDITIONS, - ); -}); - -function renderQualityGateConditions(props: Partial = {}) { - const conditions: QualityGateStatusConditionEnhanced[] = []; - for (let i = ALL_CONDITIONS; i > 0; --i) { - conditions.push( - mockQualityGateStatusConditionEnhanced({ - measure: mockMeasureEnhanced({ metric: mockMetric({ key: i.toString() }) }), - }), - ); - } - - return renderComponent( - , - ); -} diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/QualityGateSimplifiedCondition-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/QualityGateSimplifiedCondition-test.tsx deleted file mode 100644 index fffc444c8d4..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/QualityGateSimplifiedCondition-test.tsx +++ /dev/null @@ -1,67 +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 { screen } from '@testing-library/react'; -import React from 'react'; -import { mockQualityGateStatusConditionEnhanced } from '../../../../helpers/mocks/quality-gates'; -import { mockMetric } from '../../../../helpers/testMocks'; -import { renderComponent } from '../../../../helpers/testReactTestingUtils'; -import { MetricKey, MetricType } from '../../../../types/metrics'; -import { QualityGateStatusConditionEnhanced } from '../../../../types/quality-gates'; -import QualityGateCondition from '../QualityGateCondition'; -import QualityGateSimplifiedCondition from '../QualityGateSimplifiedCondition'; - -it('should show simplified condition', async () => { - renderQualityGateCondition({ - condition: quickMock(MetricKey.new_violations, MetricType.Integer), - }); - expect(await screen.findByText('metric.new_violations.name')).toBeInTheDocument(); -}); - -function renderQualityGateCondition(props: Partial) { - return renderComponent( - , - ); -} - -function quickMock( - metric: MetricKey, - type = MetricType.Rating, - addPeriod = false, - value = '3', -): QualityGateStatusConditionEnhanced { - return mockQualityGateStatusConditionEnhanced({ - error: '1', - measure: { - metric: mockMetric({ - key: metric, - name: metric, - type, - }), - value, - ...(addPeriod ? { period: { value, index: 1 } } : {}), - }, - metric, - ...(addPeriod ? { period: 1 } : {}), - }); -} diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/SonarLintPromotion-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/SonarLintPromotion-test.tsx deleted file mode 100644 index 25f8906e358..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/SonarLintPromotion-test.tsx +++ /dev/null @@ -1,67 +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 { screen } from '@testing-library/react'; -import * as React from 'react'; -import { mockQualityGateStatusCondition } from '../../../../helpers/mocks/quality-gates'; -import { mockCurrentUser } from '../../../../helpers/testMocks'; -import { renderComponent } from '../../../../helpers/testReactTestingUtils'; -import { MetricKey } from '../../../../types/metrics'; -import { SonarLintPromotion, SonarLintPromotionProps } from '../SonarLintPromotion'; - -it('should render correctly', () => { - renderSonarLintPromotion(); - expect( - screen.queryByText('overview.fix_failed_conditions_with_sonarlint'), - ).not.toBeInTheDocument(); - - renderSonarLintPromotion({ currentUser: mockCurrentUser({ usingSonarLintConnectedMode: true }) }); - expect( - screen.queryByText('overview.fix_failed_conditions_with_sonarlint'), - ).not.toBeInTheDocument(); -}); - -it.each( - [ - MetricKey.new_blocker_violations, - MetricKey.new_critical_violations, - MetricKey.new_info_violations, - MetricKey.new_violations, - MetricKey.new_major_violations, - MetricKey.new_minor_violations, - MetricKey.new_code_smells, - MetricKey.new_bugs, - MetricKey.new_vulnerabilities, - MetricKey.new_security_rating, - MetricKey.new_maintainability_rating, - MetricKey.new_reliability_rating, - ].map(Array.of), -)('should show message for %s', async (metric) => { - renderSonarLintPromotion({ - qgConditions: [mockQualityGateStatusCondition({ metric: metric as MetricKey })], - }); - - expect( - await screen.findByText('overview.fix_failed_conditions_with_sonarlint'), - ).toBeInTheDocument(); -}); - -function renderSonarLintPromotion(props: Partial = {}) { - return renderComponent(); -} diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/BranchQualityGate.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/BranchQualityGate.tsx new file mode 100644 index 00000000000..2cb03cfc314 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/BranchQualityGate.tsx @@ -0,0 +1,86 @@ +/* + * 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 { HelperHintIcon, LightPrimary, QualityGateIndicator, TextMuted } from 'design-system'; +import React from 'react'; +import { useIntl } from 'react-intl'; +import HelpTooltip from '../../../components/controls/HelpTooltip'; +import { BranchLike } from '../../../types/branch-like'; +import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates'; +import { Component, Status } from '../../../types/types'; +import BranchQualityGateConditions from './BranchQualityGateConditions'; + +interface Props { + status: Status; + branchLike?: BranchLike; + component: Pick; + failedConditions: QualityGateStatusConditionEnhanced[]; +} + +export default function BranchQualityGate(props: Readonly) { + const { status, branchLike, component, failedConditions } = props; + + return ( + <> + + + + ); +} + +function BranchQGStatus({ status }: Readonly>) { + const intl = useIntl(); + + return ( +
+ +
+
+ + + + +
+
+ + {intl.formatMessage({ id: `metric.level.${status}` })} + +
+
+
+ ); +} diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/BranchQualityGateConditions.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/BranchQualityGateConditions.tsx new file mode 100644 index 00000000000..d3bfaa0bdad --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/BranchQualityGateConditions.tsx @@ -0,0 +1,221 @@ +/* + * 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 { Badge, ButtonSecondary, themeBorder, themeColor } from 'design-system'; +import React from 'react'; +import { useIntl } from 'react-intl'; +import { + DEFAULT_ISSUES_QUERY, + isIssueMeasure, + propsToIssueParams, +} from '../../../components/shared/utils'; +import { getBranchLikeQuery } from '../../../helpers/branch-like'; +import { getLocalizedMetricName, translate } from '../../../helpers/l10n'; +import { formatMeasure, getShortType, isDiffMetric } from '../../../helpers/measures'; +import { + getComponentDrilldownUrl, + getComponentIssuesUrl, + getComponentSecurityHotspotsUrl, +} from '../../../helpers/urls'; +import { BranchLike } from '../../../types/branch-like'; +import { IssueType } from '../../../types/issues'; +import { MetricType } from '../../../types/metrics'; +import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates'; +import { Component } from '../../../types/types'; +import { + METRICS_REPORTED_IN_OVERVIEW_CARDS, + RATING_METRICS_MAPPING, + RATING_TO_SEVERITIES_MAPPING, +} from '../utils'; + +interface Props { + branchLike?: BranchLike; + component: Pick; + failedConditions: QualityGateStatusConditionEnhanced[]; +} + +export default function BranchQualityGateConditions(props: Readonly) { + const { branchLike, component, failedConditions } = props; + + const filteredFailedConditions = failedConditions.filter( + (condition) => !METRICS_REPORTED_IN_OVERVIEW_CARDS.includes(condition.metric), + ); + + return ( +
    + {filteredFailedConditions.map((condition) => ( +
  • + +
  • + ))} +
+ ); +} + +function FailedQGCondition( + props: Readonly< + Pick & { condition: QualityGateStatusConditionEnhanced } + >, +) { + const { branchLike, component, condition } = props; + const url = getQGConditionUrl(component.key, condition, branchLike); + + return ( + + + {translate('overview.measures.failed_badge')} + + + + + + ); +} + +interface FailedMetricProps { + condition: QualityGateStatusConditionEnhanced; +} + +export function FailedMetric(props: Readonly) { + const { + condition: { + measure: { metric }, + }, + } = props; + + if (metric.type === MetricType.Rating) { + return ; + } + + return ; +} + +function FailedRatingMetric({ condition }: Readonly) { + const { + error, + actual, + measure: { + metric: { type, domain }, + }, + } = condition; + const intl = useIntl(); + + return ( + <> + {intl.formatMessage( + { id: 'overview.failed_condition.x_rating_required' }, + { + rating: `${intl.formatMessage({ + id: `metric_domain.${domain}`, + })} ${intl.formatMessage({ id: 'metric.type.RATING' }).toLowerCase()}`, + value: {formatMeasure(actual, type)}, + threshold: formatMeasure(error, type), + }, + )} + + ); +} + +function FailedGeneralMetric({ condition }: Readonly) { + const { + error, + measure: { metric }, + } = condition; + const intl = useIntl(); + const measureFormattingOptions = { decimals: 2, omitExtraDecimalZeros: true }; + + return ( + <> + {intl.formatMessage( + { id: 'overview.failed_condition.x_required' }, + { + metric: ( + <> + + {formatMeasure( + condition.actual, + getShortType(metric.type), + measureFormattingOptions, + )} + + {getLocalizedMetricName(metric, true)} + + ), + threshold: ( + <> + {condition.op === 'GT' ? <>≤ : <>≥}{' '} + {formatMeasure(error, getShortType(metric.type), measureFormattingOptions)} + + ), + }, + )} + + ); +} + +function getQGConditionUrl( + componentKey: string, + condition: QualityGateStatusConditionEnhanced, + branchLike?: BranchLike, +) { + const { metric } = condition; + const sinceLeakPeriod = isDiffMetric(metric); + const ratingIssueType = RATING_METRICS_MAPPING[metric]; + + if (ratingIssueType) { + if (ratingIssueType === IssueType.SecurityHotspot) { + return getComponentSecurityHotspotsUrl(componentKey, { + ...getBranchLikeQuery(branchLike), + ...(sinceLeakPeriod ? { sinceLeakPeriod: 'true' } : {}), + }); + } + return getComponentIssuesUrl(componentKey, { + ...DEFAULT_ISSUES_QUERY, + types: ratingIssueType, + ...getBranchLikeQuery(branchLike), + ...(sinceLeakPeriod ? { sinceLeakPeriod: 'true' } : {}), + ...(ratingIssueType !== IssueType.CodeSmell + ? { severities: RATING_TO_SEVERITIES_MAPPING[Number(condition.error) - 1] } + : {}), + }); + } + + if (isIssueMeasure(condition.measure.metric.key)) { + return getComponentIssuesUrl(componentKey, { + ...propsToIssueParams(condition.measure.metric.key, condition.period != null), + ...getBranchLikeQuery(branchLike), + }); + } + + return getComponentDrilldownUrl({ + componentKey, + metric, + branchLike, + listView: true, + }); +} + +const StyledConditionButton = styled(ButtonSecondary)` + --border: ${themeBorder('default')}; +`; + +const SpanDanger = styled.span` + color: ${themeColor('danger')}; +`; diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx index 5e7c411c033..476221616e7 100644 --- a/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx +++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx @@ -29,11 +29,11 @@ import { useComponentQualityGateQuery } from '../../../queries/quality-gates'; import { PullRequest } from '../../../types/branch-like'; import { Component } from '../../../types/types'; import { AnalysisStatus } from '../components/AnalysisStatus'; -import BranchQualityGate from '../components/BranchQualityGate'; import IgnoredConditionWarning from '../components/IgnoredConditionWarning'; import ZeroNewIssuesSimplificationGuide from '../components/ZeroNewIssuesSimplificationGuide'; import '../styles.css'; import { PR_METRICS, Status } from '../utils'; +import BranchQualityGate from './BranchQualityGate'; import MeasuresCardPanel from './MeasuresCardPanel'; import PullRequestMetaTopBar from './PullRequestMetaTopBar'; import SonarLintAd from './SonarLintAd'; diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/BranchQualityGate-it.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/BranchQualityGate-it.tsx new file mode 100644 index 00000000000..a841f4d4436 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/BranchQualityGate-it.tsx @@ -0,0 +1,148 @@ +/* + * 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 { mockPullRequest } from '../../../../helpers/mocks/branch-like'; +import { mockComponent } from '../../../../helpers/mocks/component'; +import { mockQualityGateStatusConditionEnhanced } from '../../../../helpers/mocks/quality-gates'; +import { mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks'; +import { renderComponent } from '../../../../helpers/testReactTestingUtils'; +import { byLabelText, byRole } from '../../../../helpers/testSelector'; +import { MetricKey, MetricType } from '../../../../types/metrics'; +import { FCProps } from '../../../../types/misc'; +import { Status } from '../../utils'; +import BranchQualityGate from '../BranchQualityGate'; + +it('renders failed QG', () => { + renderBranchQualityGate(); + + // Maintainability rating condition + const maintainabilityRatingLink = byRole('link', { + name: 'overview.measures.failed_badge overview.failed_condition.x_rating_requiredmetric_domain.Maintainability metric.type.rating E A', + }).get(); + expect(maintainabilityRatingLink).toBeInTheDocument(); + expect(maintainabilityRatingLink).toHaveAttribute( + 'href', + '/project/issues?issueStatuses=OPEN%2CCONFIRMED&types=CODE_SMELL&pullRequest=1001&sinceLeakPeriod=true&id=my-project', + ); + + // Security Hotspots rating condition + const securityHotspotsRatingLink = byRole('link', { + name: 'overview.measures.failed_badge overview.failed_condition.x_rating_requiredmetric_domain.Security Review metric.type.rating E A', + }).get(); + expect(securityHotspotsRatingLink).toBeInTheDocument(); + expect(securityHotspotsRatingLink).toHaveAttribute( + 'href', + '/security_hotspots?id=my-project&pullRequest=1001', + ); + + // New code smells + const codeSmellsLink = byRole('link', { + name: 'overview.measures.failed_badge overview.failed_condition.x_required 5 Code Smells≤ 1', + }).get(); + expect(codeSmellsLink).toBeInTheDocument(); + expect(codeSmellsLink).toHaveAttribute( + 'href', + '/project/issues?issueStatuses=OPEN%2CCONFIRMED&types=CODE_SMELL&pullRequest=1001&id=my-project', + ); + + // Conditions to cover + const conditionToCoverLink = byRole('link', { + name: 'overview.measures.failed_badge overview.failed_condition.x_required 5 Conditions to cover≥ 10', + }).get(); + expect(conditionToCoverLink).toBeInTheDocument(); + expect(conditionToCoverLink).toHaveAttribute( + 'href', + '/component_measures?id=my-project&metric=conditions_to_cover&pullRequest=1001&view=list', + ); + + expect(byLabelText('overview.quality_gate_x.overview.gate.ERROR').get()).toBeInTheDocument(); +}); + +it('renders passed QG', () => { + renderBranchQualityGate({ failedConditions: [], status: Status.OK }); + + expect(byLabelText('overview.quality_gate_x.overview.gate.OK').get()).toBeInTheDocument(); + expect(byRole('link').query()).not.toBeInTheDocument(); +}); + +function renderBranchQualityGate(props: Partial> = {}) { + return renderComponent( + , + ); +}