]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21455 Drop dead code & Re-organize branch/pr overview components to relevant...
author7PH <benjamin.raymond@sonarsource.com>
Mon, 29 Jan 2024 13:21:35 +0000 (14:21 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 31 Jan 2024 20:03:37 +0000 (20:03 +0000)
43 files changed:
server/sonar-web/design-system/src/theme/light.ts
server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx
server/sonar-web/src/main/js/apps/overview/branches/DebtValue.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/branches/DrilldownMeasureValue.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelCard.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/branches/QualityGateConditions.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx
server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanelSection.tsx
server/sonar-web/src/main/js/apps/overview/branches/QualityGateSimplifiedCondition.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusHeader.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusPassedView.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusTitle.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/branches/SonarLintPromotion.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/branches/__tests__/DebtValue-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateCondition-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateConditions-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateSimplifiedCondition-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/branches/__tests__/SonarLintPromotion-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/components/BranchQualityGate.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/components/BranchQualityGateConditions.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/components/IssueRating.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/components/QualityGateCondition.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/components/QualityGateConditions.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/components/QualityGateSimplifiedCondition.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/components/QualityGateStatusHeader.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/components/QualityGateStatusPassedView.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/components/QualityGateStatusTitle.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/components/SonarLintPromotion.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/components/__tests__/BranchQualityGate-it.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueLabel-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueRating-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/components/__tests__/QualityGateCondition-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/components/__tests__/QualityGateConditions-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/components/__tests__/QualityGateSimplifiedCondition-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/components/__tests__/SonarLintPromotion-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/pullRequests/BranchQualityGate.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/pullRequests/BranchQualityGateConditions.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx
server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/BranchQualityGate-it.tsx [new file with mode: 0644]

index d6ba2162d3b86575943e0598d575f1e0660afc7a..dbce87280d12266265b395d2e27ee793fa8ead7c 100644 (file)
@@ -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],
index 4a2efcc260ba0338cfc33f83d3debf685bf9363d..1a6e477c88bbc4596fc9958f9f37278fbf928829 100644 (file)
@@ -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 (file)
index b719d2c..0000000
+++ /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 ? (
-        <span aria-label={translate('no_data')} className="overview-measures-empty-value" />
-      ) : (
-        <DrilldownLink
-          ariaLabel={translateWithParameters(
-            'overview.see_more_details_on_x_of_y',
-            formattedValue,
-            metricName,
-          )}
-          branchLike={branchLike}
-          className="overview-measures-value text-light"
-          component={component.key}
-          metric={metricKey}
-        >
-          {formattedValue}
-        </DrilldownLink>
-      )}
-      <span className="big-spacer-left">{metricName}</span>
-    </>
-  );
-}
-
-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 (file)
index 97dfdb5..0000000
+++ /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 <span>–</span>;
-  }
-
-  const url = getComponentDrilldownUrl({
-    branchLike,
-    componentKey: component.key,
-    metric,
-  });
-
-  return (
-    <span>
-      <DrilldownLink
-        aria-label={translateWithParameters(
-          'overview.see_more_details_on_x_y',
-          measure.value,
-          localizeMetric(metric),
-        )}
-        to={url}
-      >
-        {formatMeasure(measure.value, MetricType.ShortInteger)}
-      </DrilldownLink>
-    </span>
-  );
-}
-
-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 (file)
index dc0dba1..0000000
+++ /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<Props & React.HTMLAttributes<HTMLDivElement>>,
-) {
-  const { category, children, rating, ...attributes } = props;
-
-  return (
-    <div className="sw-flex sw-justify-between sw-items-center" {...attributes}>
-      <div className="sw-flex sw-flex-col sw-justify-between">
-        <div className="sw-body-sm-highlight sw-flex sw-items-center">{category}</div>
-
-        <div className="sw-mt-3">{children}</div>
-      </div>
-
-      <div>{rating}</div>
-    </div>
-  );
-}
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 (file)
index 0000000..dda75f6
--- /dev/null
@@ -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<Component, 'key'>;
+  condition: QualityGateStatusConditionEnhanced;
+}
+
+export default class QualityGateCondition extends React.PureComponent<Props> {
+  getIssuesUrl = (inNewCodePeriod: boolean, customQuery: Dict<string>) => {
+    const query: Dict<string | undefined> = {
+      ...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<string | undefined> = {
+      ...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 <LinkBox to={METRICS_TO_URL_MAPPING[metricKey]()}>{children}</LinkBox>;
+    }
+
+    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 <LinkBox to={url}>{children}</LinkBox>;
+  }
+
+  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(
+      <div className="sw-flex sw-items-center sw-p-2">
+        <MeasureIndicator
+          className="sw-flex sw-justify-center sw-w-6 sw-mx-4"
+          decimals={2}
+          metricKey={measure.metric.key}
+          metricType={measure.metric.type}
+          value={actual}
+        />
+        <div className="sw-flex sw-flex-col sw-text-sm">
+          <div className="sw-flex sw-items-center">
+            <IssueTypeIcon className="sw-mr-2" query={metric.key} />
+            <span className="sw-body-sm-highlight sw-text-ellipsis sw-max-w-abs-300">
+              {this.getPrimaryText()}
+            </span>
+          </div>
+          <TextMuted text={`${operator} ${formatMeasure(threshold, metric.type)}`} />
+        </div>
+      </div>,
+    );
+  }
+}
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 (file)
index 0000000..de461bf
--- /dev/null
@@ -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<Component, 'key'>;
+  collapsible?: boolean;
+  failedConditions: QualityGateStatusConditionEnhanced[];
+  isBuiltInQualityGate?: boolean;
+}
+
+const MAX_CONDITIONS = 5;
+
+export function QualityGateConditions(props: Readonly<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 (
+    <ul id="overview-quality-gate-conditions-list" className="sw-mb-2">
+      {renderConditions.map((condition) => (
+        <div key={condition.measure.metric.key}>
+          {isSimplifiedCondition(condition) ? (
+            <QualityGateSimplifiedCondition
+              branchLike={branchLike}
+              component={component}
+              condition={condition}
+            />
+          ) : (
+            <QualityGateCondition
+              branchLike={branchLike}
+              component={component}
+              condition={condition}
+            />
+          )}
+          <BasicSeparator />
+        </div>
+      ))}
+      {renderCollapsed && (
+        <li className="sw-flex sw-justify-center sw-my-3">
+          <Link onClick={handleToggleCollapsed} to={{}} preventDefault>
+            <span className="sw-font-semibold sw-text-sm">{translate('show_more')}</span>
+          </Link>
+        </li>
+      )}
+    </ul>
+  );
+}
+
+export default React.memo(QualityGateConditions);
index 0118908120d05abe26b5767f1bb8a2933c46d3e0..d752b0f4b23d017089b4f3b3ee86d8d6d8fa17df 100644 (file)
@@ -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<Component, 'key' | 'qualifier' | 'qualityGate'>;
index 96a9057782c8b4eb8c3ee6c0f6b39ba0405be350..d591c286381e74129484e39e7f3b825a196f128e 100644 (file)
@@ -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 (file)
index 0000000..d4172a7
--- /dev/null
@@ -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<Component, 'key'>;
+  condition: QualityGateStatusConditionEnhanced;
+}
+
+export default function QualityGateSimplifiedCondition({
+  branchLike,
+  component,
+  condition,
+}: Readonly<Props>) {
+  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 (
+    <LinkBox
+      to={getComponentIssuesUrl(component.key, {
+        ...propsToIssueParams(condition.measure.metric.key, condition.period != null),
+        ...getBranchLikeQuery(branchLike),
+      })}
+    >
+      <div className="sw-flex sw-p-2 sw-items-baseline">
+        <Highlight className="sw-mx-4 sw-w-6 sw-my-0 sw-text-right">{formattedValue}</Highlight>
+        <Highlight
+          className="sw-text-ellipsis sw-pr-4"
+          data-guiding-id={
+            metric.key === MetricKey.new_violations
+              ? 'overviewZeroNewIssuesSimplification'
+              : undefined
+          }
+        >
+          {getPrimaryText()}
+        </Highlight>
+      </div>
+    </LinkBox>
+  );
+}
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 (file)
index 0000000..c4eee2f
--- /dev/null
@@ -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 (
+    <div className="sw-flex sw-items-center sw-mb-4">
+      <QualityGateIndicator status={status} className="sw-mr-2" size="xl" />
+      <div className="sw-flex sw-flex-col">
+        <span className="sw-heading-lg">{translate('metric.level', status)}</span>
+        {failedConditionCount > 0 && (
+          <TextError
+            className="sw-font-regular"
+            text={intl.formatMessage(
+              { id: 'overview.X_conditions_failed' },
+              {
+                conditions: <strong>{failedConditionCount}</strong>,
+              },
+            )}
+          />
+        )}
+      </div>
+    </div>
+  );
+}
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 (file)
index 0000000..2a60ea6
--- /dev/null
@@ -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 (
+    <div className="sw-flex sw-items-center sw-justify-center sw-flex-col">
+      <OverviewQGPassedIcon className="sw-my-12" />
+      <p className="sw-mb-8">{translate('overview.passed.clean_code')}</p>
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusTitle.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusTitle.tsx
new file mode 100644 (file)
index 0000000..14e985c
--- /dev/null
@@ -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 (
+    <>
+      <div className="sw-flex sw-items-center sw-mb-4 sw--mt-2">
+        <div className="sw-flex sw-items-center">
+          <PageTitle as="h2" text={translate('overview.quality_gate.status')} />
+          <HelpTooltip
+            className="sw-ml-2"
+            overlay={<div className="sw-my-4">{translate('overview.quality_gate.help')}</div>}
+          >
+            <HelperHintIcon aria-label="help-tooltip" />
+          </HelpTooltip>
+        </div>
+      </div>
+      <BasicSeparator className="sw--mx-6" />
+    </>
+  );
+}
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 (file)
index 0000000..dbcf3ec
--- /dev/null
@@ -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 (
+    <Card className="it__overview__sonarlint-promotion sw-my-4 sw-body-sm">
+      <FormattedMessage
+        id="overview.fix_failed_conditions_with_sonarlint"
+        defaultMessage={translate('overview.fix_failed_conditions_with_sonarlint')}
+        values={{
+          link: (
+            <>
+              <DiscreetLink
+                to="https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=sonarqube"
+                rel="noopener noreferrer"
+                target="_blank"
+                showExternalIcon={false}
+                className="sw-mr-1"
+              >
+                SonarLint
+              </DiscreetLink>
+              <span className="sw-align-middle">
+                <SonarLintIcon size={16} />
+              </span>
+            </>
+          ),
+        }}
+      />
+    </Card>
+  );
+}
+
+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 (file)
index e528610..0000000
+++ /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<DebtValueProps> = {}) {
-  return renderComponent(
-    <DebtValue
-      branchLike={mockMainBranch()}
-      component={mockComponent()}
-      measures={[
-        mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.sqale_index }) }),
-        mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_technical_debt }) }),
-      ]}
-      {...props}
-    />,
-  );
-}
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 (file)
index 0000000..85d384b
--- /dev/null
@@ -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<QualityGateCondition['props']>) {
+  return renderComponent(
+    <QualityGateCondition
+      component={{ key: 'abcd-key' }}
+      condition={mockQualityGateStatusConditionEnhanced()}
+      {...props}
+    />,
+  );
+}
+
+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 (file)
index 0000000..46697d0
--- /dev/null
@@ -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<QualityGateConditionsProps> = {}) {
+  const conditions: QualityGateStatusConditionEnhanced[] = [];
+  for (let i = ALL_CONDITIONS; i > 0; --i) {
+    conditions.push(
+      mockQualityGateStatusConditionEnhanced({
+        measure: mockMeasureEnhanced({ metric: mockMetric({ key: i.toString() }) }),
+      }),
+    );
+  }
+
+  return renderComponent(
+    <QualityGateConditions component={mockComponent()} failedConditions={conditions} {...props} />,
+  );
+}
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 (file)
index 0000000..fffc444
--- /dev/null
@@ -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<QualityGateCondition['props']>) {
+  return renderComponent(
+    <QualityGateSimplifiedCondition
+      component={{ key: 'abcd-key' }}
+      condition={mockQualityGateStatusConditionEnhanced()}
+      {...props}
+    />,
+  );
+}
+
+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 (file)
index 0000000..25f8906
--- /dev/null
@@ -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<SonarLintPromotionProps> = {}) {
+  return renderComponent(<SonarLintPromotion currentUser={mockCurrentUser()} {...props} />);
+}
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 (file)
index 2cb03cf..0000000
+++ /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<Component, 'key'>;
-  failedConditions: QualityGateStatusConditionEnhanced[];
-}
-
-export default function BranchQualityGate(props: Readonly<Props>) {
-  const { status, branchLike, component, failedConditions } = props;
-
-  return (
-    <>
-      <BranchQGStatus status={status} />
-      <BranchQualityGateConditions
-        branchLike={branchLike}
-        component={component}
-        failedConditions={failedConditions}
-      />
-    </>
-  );
-}
-
-function BranchQGStatus({ status }: Readonly<Pick<Props, 'status'>>) {
-  const intl = useIntl();
-
-  return (
-    <div className="sw-flex sw-items-center sw-mb-5">
-      <QualityGateIndicator
-        status={status}
-        className="sw-mr-2"
-        size="xl"
-        ariaLabel={intl.formatMessage(
-          { id: 'overview.quality_gate_x' },
-          { '0': intl.formatMessage({ id: `overview.gate.${status}` }) },
-        )}
-      />
-      <div className="sw-flex sw-flex-col sw-justify-around">
-        <div className="sw-flex sw-items-center">
-          <TextMuted
-            className="sw-body-sm"
-            text={intl.formatMessage({ id: 'overview.quality_gate' })}
-          />
-          <HelpTooltip
-            className="sw-ml-2"
-            overlay={intl.formatMessage({ id: 'overview.quality_gate.help' })}
-          >
-            <HelperHintIcon aria-label="help-tooltip" />
-          </HelpTooltip>
-        </div>
-        <div>
-          <LightPrimary as="h1" className="sw-heading-xl">
-            {intl.formatMessage({ id: `metric.level.${status}` })}
-          </LightPrimary>
-        </div>
-      </div>
-    </div>
-  );
-}
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 (file)
index d3bfaa0..0000000
+++ /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<Component, 'key'>;
-  failedConditions: QualityGateStatusConditionEnhanced[];
-}
-
-export default function BranchQualityGateConditions(props: Readonly<Props>) {
-  const { branchLike, component, failedConditions } = props;
-
-  const filteredFailedConditions = failedConditions.filter(
-    (condition) => !METRICS_REPORTED_IN_OVERVIEW_CARDS.includes(condition.metric),
-  );
-
-  return (
-    <ul className="sw-flex sw-items-center sw-gap-2 sw-flex-wrap sw-mb-4">
-      {filteredFailedConditions.map((condition) => (
-        <li key={condition.metric}>
-          <FailedQGCondition branchLike={branchLike} component={component} condition={condition} />
-        </li>
-      ))}
-    </ul>
-  );
-}
-
-function FailedQGCondition(
-  props: Readonly<
-    Pick<Props, 'branchLike' | 'component'> & { condition: QualityGateStatusConditionEnhanced }
-  >,
-) {
-  const { branchLike, component, condition } = props;
-  const url = getQGConditionUrl(component.key, condition, branchLike);
-
-  return (
-    <StyledConditionButton className="sw-px-3 sw-py-2 sw-rounded-1 sw-body-sm" to={url}>
-      <Badge className="sw-mr-2 sw-px-1" variant="deleted">
-        {translate('overview.measures.failed_badge')}
-      </Badge>
-      <SpanDanger>
-        <FailedMetric condition={condition} />
-      </SpanDanger>
-    </StyledConditionButton>
-  );
-}
-
-interface FailedMetricProps {
-  condition: QualityGateStatusConditionEnhanced;
-}
-
-export function FailedMetric(props: Readonly<FailedMetricProps>) {
-  const {
-    condition: {
-      measure: { metric },
-    },
-  } = props;
-
-  if (metric.type === MetricType.Rating) {
-    return <FailedRatingMetric {...props} />;
-  }
-
-  return <FailedGeneralMetric {...props} />;
-}
-
-function FailedRatingMetric({ condition }: Readonly<FailedMetricProps>) {
-  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: <strong className="sw-body-sm-highlight">{formatMeasure(actual, type)}</strong>,
-          threshold: formatMeasure(error, type),
-        },
-      )}
-    </>
-  );
-}
-
-function FailedGeneralMetric({ condition }: Readonly<FailedMetricProps>) {
-  const {
-    error,
-    measure: { metric },
-  } = condition;
-  const intl = useIntl();
-  const measureFormattingOptions = { decimals: 2, omitExtraDecimalZeros: true };
-
-  return (
-    <>
-      {intl.formatMessage(
-        { id: 'overview.failed_condition.x_required' },
-        {
-          metric: (
-            <>
-              <strong className="sw-body-sm-highlight sw-mr-1">
-                {formatMeasure(
-                  condition.actual,
-                  getShortType(metric.type),
-                  measureFormattingOptions,
-                )}
-              </strong>
-              {getLocalizedMetricName(metric, true)}
-            </>
-          ),
-          threshold: (
-            <>
-              {condition.op === 'GT' ? <>&le;</> : <>&ge;</>}{' '}
-              {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 (file)
index 6dd83fc..0000000
+++ /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 (
-    <div className="sw-body-md sw-flex sw-items-center">
-      {value === undefined ? (
-        <LightLabel aria-label={translate('no_data')}> — </LightLabel>
-      ) : (
-        <Tooltip
-          classNameSpace={disabled ? 'tooltip' : 'sw-hidden'}
-          overlay={<OverviewDisabledLinkTooltip />}
-        >
-          <DrilldownLink className="it__overview-measures-value" {...drilldownLinkProps}>
-            {formatMeasure(value, MetricType.ShortInteger)}
-          </DrilldownLink>
-        </Tooltip>
-      )}
-
-      <LightLabel className="sw-mx-2">{localizeMetric(metricKey)}</LightLabel>
-
-      {helpTooltip && (
-        <HelpTooltip overlay={helpTooltip}>
-          <HelperHintIcon aria-label={helpTooltip} />
-        </HelpTooltip>
-      )}
-    </div>
-  );
-}
-
-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 (file)
index f22c305..0000000
+++ /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 <NoRating />;
-  }
-
-  return (
-    <Tooltip overlay={rawValue && <RatingTooltipContent metricKey={ratingKey} value={rawValue} />}>
-      <span>
-        {value ? (
-          <DiscreetLinkBox
-            to={getComponentDrilldownUrl({
-              branchLike,
-              componentKey: component.key,
-              metric: ratingKey,
-              listView: true,
-            })}
-          >
-            <MetricsRatingBadge
-              label={translateWithParameters('metric.has_rating_X', value)}
-              rating={value}
-              size="md"
-            />
-          </DiscreetLinkBox>
-        ) : (
-          <NoRating />
-        )}
-      </span>
-    </Tooltip>
-  );
-}
-
-export default IssueRating;
-
-function NoRating() {
-  return <div className="sw-w-8 sw-h-8 sw-flex sw-justify-center sw-items-center">–</div>;
-}
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 (file)
index 02963e5..0000000
+++ /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<boolean> = {
-  manual_baseline: true,
-  SPECIFIC_ANALYSIS: true,
-};
-
-export class LeakPeriodLegend extends React.PureComponent<Props & WrappedComponentProps> {
-  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 (
-        <div className="overview-legend overview-legend-spaced-line">
-          {translateWithParameters('overview.new_code_period_x', leakPeriodLabel)}
-        </div>
-      );
-    }
-
-    const leakPeriodDate = getNewCodePeriodDate(period);
-    if (!leakPeriodDate) {
-      return null;
-    }
-
-    const formattedDateFunction = (formattedLeakPeriodDate: string) => (
-      <span>
-        {translateWithParameters(
-          period.mode === 'previous_analysis'
-            ? 'overview.previous_analysis_on_x'
-            : 'overview.started_on_x',
-          formattedLeakPeriodDate,
-        )}
-      </span>
-    );
-
-    const tooltip =
-      differenceInDays(new Date(), leakPeriodDate) < 1 ? (
-        <DateTimeFormatter date={leakPeriodDate}>{formattedDateFunction}</DateTimeFormatter>
-      ) : (
-        <DateFormatter date={leakPeriodDate} long>
-          {formattedDateFunction}
-        </DateFormatter>
-      );
-
-    return (
-      <Tooltip overlay={tooltip}>
-        <div className="overview-legend">
-          {translateWithParameters('overview.new_code_period_x', leakPeriodLabel)}
-          <br />
-          <DateFromNow date={leakPeriodDate}>
-            {(fromNow) => (
-              <span className="note">
-                {translateWithParameters(
-                  period.mode === 'previous_analysis'
-                    ? 'overview.previous_analysis_x'
-                    : 'overview.started_x',
-                  fromNow,
-                )}
-              </span>
-            )}
-          </DateFromNow>
-        </div>
-      </Tooltip>
-    );
-  }
-}
-
-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 (file)
index dda75f6..0000000
+++ /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<Component, 'key'>;
-  condition: QualityGateStatusConditionEnhanced;
-}
-
-export default class QualityGateCondition extends React.PureComponent<Props> {
-  getIssuesUrl = (inNewCodePeriod: boolean, customQuery: Dict<string>) => {
-    const query: Dict<string | undefined> = {
-      ...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<string | undefined> = {
-      ...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 <LinkBox to={METRICS_TO_URL_MAPPING[metricKey]()}>{children}</LinkBox>;
-    }
-
-    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 <LinkBox to={url}>{children}</LinkBox>;
-  }
-
-  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(
-      <div className="sw-flex sw-items-center sw-p-2">
-        <MeasureIndicator
-          className="sw-flex sw-justify-center sw-w-6 sw-mx-4"
-          decimals={2}
-          metricKey={measure.metric.key}
-          metricType={measure.metric.type}
-          value={actual}
-        />
-        <div className="sw-flex sw-flex-col sw-text-sm">
-          <div className="sw-flex sw-items-center">
-            <IssueTypeIcon className="sw-mr-2" query={metric.key} />
-            <span className="sw-body-sm-highlight sw-text-ellipsis sw-max-w-abs-300">
-              {this.getPrimaryText()}
-            </span>
-          </div>
-          <TextMuted text={`${operator} ${formatMeasure(threshold, metric.type)}`} />
-        </div>
-      </div>,
-    );
-  }
-}
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 (file)
index b74ffa8..0000000
+++ /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<Component, 'key'>;
-  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 (
-    <ul id="overview-quality-gate-conditions-list" className="sw-mb-2">
-      {renderConditions.map((condition) => (
-        <div key={condition.measure.metric.key}>
-          {isSimplifiedCondition(condition) ? (
-            <QualityGateSimplifiedCondition
-              branchLike={branchLike}
-              component={component}
-              condition={condition}
-            />
-          ) : (
-            <QualityGateCondition
-              branchLike={branchLike}
-              component={component}
-              condition={condition}
-            />
-          )}
-          <BasicSeparator />
-        </div>
-      ))}
-      {renderCollapsed && (
-        <li className="sw-flex sw-justify-center sw-my-3">
-          <Link onClick={handleToggleCollapsed} to={{}} preventDefault>
-            <span className="sw-font-semibold sw-text-sm">{translate('show_more')}</span>
-          </Link>
-        </li>
-      )}
-    </ul>
-  );
-}
-
-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 (file)
index d4172a7..0000000
+++ /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<Component, 'key'>;
-  condition: QualityGateStatusConditionEnhanced;
-}
-
-export default function QualityGateSimplifiedCondition({
-  branchLike,
-  component,
-  condition,
-}: Readonly<Props>) {
-  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 (
-    <LinkBox
-      to={getComponentIssuesUrl(component.key, {
-        ...propsToIssueParams(condition.measure.metric.key, condition.period != null),
-        ...getBranchLikeQuery(branchLike),
-      })}
-    >
-      <div className="sw-flex sw-p-2 sw-items-baseline">
-        <Highlight className="sw-mx-4 sw-w-6 sw-my-0 sw-text-right">{formattedValue}</Highlight>
-        <Highlight
-          className="sw-text-ellipsis sw-pr-4"
-          data-guiding-id={
-            metric.key === MetricKey.new_violations
-              ? 'overviewZeroNewIssuesSimplification'
-              : undefined
-          }
-        >
-          {getPrimaryText()}
-        </Highlight>
-      </div>
-    </LinkBox>
-  );
-}
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 (file)
index c4eee2f..0000000
+++ /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 (
-    <div className="sw-flex sw-items-center sw-mb-4">
-      <QualityGateIndicator status={status} className="sw-mr-2" size="xl" />
-      <div className="sw-flex sw-flex-col">
-        <span className="sw-heading-lg">{translate('metric.level', status)}</span>
-        {failedConditionCount > 0 && (
-          <TextError
-            className="sw-font-regular"
-            text={intl.formatMessage(
-              { id: 'overview.X_conditions_failed' },
-              {
-                conditions: <strong>{failedConditionCount}</strong>,
-              },
-            )}
-          />
-        )}
-      </div>
-    </div>
-  );
-}
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 (file)
index 2a60ea6..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import { OverviewQGPassedIcon } from 'design-system';
-import React from 'react';
-import { translate } from '../../../helpers/l10n';
-
-export default function QualityGateStatusPassedView() {
-  return (
-    <div className="sw-flex sw-items-center sw-justify-center sw-flex-col">
-      <OverviewQGPassedIcon className="sw-my-12" />
-      <p className="sw-mb-8">{translate('overview.passed.clean_code')}</p>
-    </div>
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/overview/components/QualityGateStatusTitle.tsx b/server/sonar-web/src/main/js/apps/overview/components/QualityGateStatusTitle.tsx
deleted file mode 100644 (file)
index 14e985c..0000000
+++ /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 (
-    <>
-      <div className="sw-flex sw-items-center sw-mb-4 sw--mt-2">
-        <div className="sw-flex sw-items-center">
-          <PageTitle as="h2" text={translate('overview.quality_gate.status')} />
-          <HelpTooltip
-            className="sw-ml-2"
-            overlay={<div className="sw-my-4">{translate('overview.quality_gate.help')}</div>}
-          >
-            <HelperHintIcon aria-label="help-tooltip" />
-          </HelpTooltip>
-        </div>
-      </div>
-      <BasicSeparator className="sw--mx-6" />
-    </>
-  );
-}
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 (file)
index dbcf3ec..0000000
+++ /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 (
-    <Card className="it__overview__sonarlint-promotion sw-my-4 sw-body-sm">
-      <FormattedMessage
-        id="overview.fix_failed_conditions_with_sonarlint"
-        defaultMessage={translate('overview.fix_failed_conditions_with_sonarlint')}
-        values={{
-          link: (
-            <>
-              <DiscreetLink
-                to="https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=sonarqube"
-                rel="noopener noreferrer"
-                target="_blank"
-                showExternalIcon={false}
-                className="sw-mr-1"
-              >
-                SonarLint
-              </DiscreetLink>
-              <span className="sw-align-middle">
-                <SonarLintIcon size={16} />
-              </span>
-            </>
-          ),
-        }}
-      />
-    </Card>
-  );
-}
-
-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 (file)
index a841f4d..0000000
+++ /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<FCProps<typeof BranchQualityGate>> = {}) {
-  return renderComponent(
-    <BranchQualityGate
-      status={Status.ERROR}
-      branchLike={mockPullRequest()}
-      component={mockComponent()}
-      failedConditions={[
-        mockQualityGateStatusConditionEnhanced({
-          actual: '5.0',
-          error: '1.0',
-          metric: MetricKey.new_maintainability_rating,
-          measure: mockMeasureEnhanced({
-            metric: mockMetric({
-              domain: 'Maintainability',
-              key: MetricKey.new_maintainability_rating,
-              name: 'Maintainability rating',
-              type: MetricType.Rating,
-            }),
-          }),
-        }),
-        mockQualityGateStatusConditionEnhanced({
-          actual: '5.0',
-          error: '1.0',
-          metric: MetricKey.new_security_review_rating,
-          measure: mockMeasureEnhanced({
-            metric: mockMetric({
-              domain: 'Security Review',
-              key: MetricKey.new_security_review_rating,
-              name: 'Security Review Rating',
-              type: MetricType.Rating,
-            }),
-          }),
-        }),
-        mockQualityGateStatusConditionEnhanced({
-          actual: '5',
-          error: '1',
-          metric: MetricKey.new_code_smells,
-          measure: mockMeasureEnhanced({
-            metric: mockMetric({
-              domain: 'Maintainability',
-              key: MetricKey.new_code_smells,
-              name: 'Code Smells',
-              type: MetricType.ShortInteger,
-            }),
-          }),
-        }),
-        mockQualityGateStatusConditionEnhanced({
-          actual: '5',
-          error: '10',
-          op: 'up',
-          metric: MetricKey.conditions_to_cover,
-          measure: mockMeasureEnhanced({
-            metric: mockMetric({
-              key: MetricKey.conditions_to_cover,
-              name: 'Conditions to cover',
-              type: MetricType.ShortInteger,
-            }),
-          }),
-        }),
-      ]}
-      {...props}
-    />,
-  );
-}
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 (file)
index fda48d8..0000000
+++ /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<IssueLabelProps> = {}) {
-  return renderComponent(
-    <IssueLabel
-      branchLike={mockPullRequest()}
-      component={mockComponent()}
-      measures={[]}
-      type={IssueType.Bug}
-      {...props}
-    />,
-  );
-}
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 (file)
index d2d2f5b..0000000
+++ /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<IssueRatingProps> = {}) {
-  return renderComponent(
-    <IssueRating
-      branchLike={mockPullRequest()}
-      component={mockComponent()}
-      measures={[
-        mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_reliability_rating }) }),
-        mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.reliability_rating }) }),
-        mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_maintainability_rating }) }),
-        mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.sqale_rating }) }),
-        mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_security_rating }) }),
-        mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.security_rating }) }),
-      ]}
-      type={IssueType.Bug}
-      {...props}
-    />,
-  );
-}
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 (file)
index 0d322aa..0000000
+++ /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<any>).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<Period> = {}) {
-  return renderComponent(
-    <LeakPeriodLegend
-      intl={
-        {
-          formatDate: (date: string) => '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 (file)
index 85d384b..0000000
+++ /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<QualityGateCondition['props']>) {
-  return renderComponent(
-    <QualityGateCondition
-      component={{ key: 'abcd-key' }}
-      condition={mockQualityGateStatusConditionEnhanced()}
-      {...props}
-    />,
-  );
-}
-
-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 (file)
index 46697d0..0000000
+++ /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<QualityGateConditionsProps> = {}) {
-  const conditions: QualityGateStatusConditionEnhanced[] = [];
-  for (let i = ALL_CONDITIONS; i > 0; --i) {
-    conditions.push(
-      mockQualityGateStatusConditionEnhanced({
-        measure: mockMeasureEnhanced({ metric: mockMetric({ key: i.toString() }) }),
-      }),
-    );
-  }
-
-  return renderComponent(
-    <QualityGateConditions component={mockComponent()} failedConditions={conditions} {...props} />,
-  );
-}
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 (file)
index fffc444..0000000
+++ /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<QualityGateCondition['props']>) {
-  return renderComponent(
-    <QualityGateSimplifiedCondition
-      component={{ key: 'abcd-key' }}
-      condition={mockQualityGateStatusConditionEnhanced()}
-      {...props}
-    />,
-  );
-}
-
-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 (file)
index 25f8906..0000000
+++ /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<SonarLintPromotionProps> = {}) {
-  return renderComponent(<SonarLintPromotion currentUser={mockCurrentUser()} {...props} />);
-}
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 (file)
index 0000000..2cb03cf
--- /dev/null
@@ -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<Component, 'key'>;
+  failedConditions: QualityGateStatusConditionEnhanced[];
+}
+
+export default function BranchQualityGate(props: Readonly<Props>) {
+  const { status, branchLike, component, failedConditions } = props;
+
+  return (
+    <>
+      <BranchQGStatus status={status} />
+      <BranchQualityGateConditions
+        branchLike={branchLike}
+        component={component}
+        failedConditions={failedConditions}
+      />
+    </>
+  );
+}
+
+function BranchQGStatus({ status }: Readonly<Pick<Props, 'status'>>) {
+  const intl = useIntl();
+
+  return (
+    <div className="sw-flex sw-items-center sw-mb-5">
+      <QualityGateIndicator
+        status={status}
+        className="sw-mr-2"
+        size="xl"
+        ariaLabel={intl.formatMessage(
+          { id: 'overview.quality_gate_x' },
+          { '0': intl.formatMessage({ id: `overview.gate.${status}` }) },
+        )}
+      />
+      <div className="sw-flex sw-flex-col sw-justify-around">
+        <div className="sw-flex sw-items-center">
+          <TextMuted
+            className="sw-body-sm"
+            text={intl.formatMessage({ id: 'overview.quality_gate' })}
+          />
+          <HelpTooltip
+            className="sw-ml-2"
+            overlay={intl.formatMessage({ id: 'overview.quality_gate.help' })}
+          >
+            <HelperHintIcon aria-label="help-tooltip" />
+          </HelpTooltip>
+        </div>
+        <div>
+          <LightPrimary as="h1" className="sw-heading-xl">
+            {intl.formatMessage({ id: `metric.level.${status}` })}
+          </LightPrimary>
+        </div>
+      </div>
+    </div>
+  );
+}
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 (file)
index 0000000..d3bfaa0
--- /dev/null
@@ -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<Component, 'key'>;
+  failedConditions: QualityGateStatusConditionEnhanced[];
+}
+
+export default function BranchQualityGateConditions(props: Readonly<Props>) {
+  const { branchLike, component, failedConditions } = props;
+
+  const filteredFailedConditions = failedConditions.filter(
+    (condition) => !METRICS_REPORTED_IN_OVERVIEW_CARDS.includes(condition.metric),
+  );
+
+  return (
+    <ul className="sw-flex sw-items-center sw-gap-2 sw-flex-wrap sw-mb-4">
+      {filteredFailedConditions.map((condition) => (
+        <li key={condition.metric}>
+          <FailedQGCondition branchLike={branchLike} component={component} condition={condition} />
+        </li>
+      ))}
+    </ul>
+  );
+}
+
+function FailedQGCondition(
+  props: Readonly<
+    Pick<Props, 'branchLike' | 'component'> & { condition: QualityGateStatusConditionEnhanced }
+  >,
+) {
+  const { branchLike, component, condition } = props;
+  const url = getQGConditionUrl(component.key, condition, branchLike);
+
+  return (
+    <StyledConditionButton className="sw-px-3 sw-py-2 sw-rounded-1 sw-body-sm" to={url}>
+      <Badge className="sw-mr-2 sw-px-1" variant="deleted">
+        {translate('overview.measures.failed_badge')}
+      </Badge>
+      <SpanDanger>
+        <FailedMetric condition={condition} />
+      </SpanDanger>
+    </StyledConditionButton>
+  );
+}
+
+interface FailedMetricProps {
+  condition: QualityGateStatusConditionEnhanced;
+}
+
+export function FailedMetric(props: Readonly<FailedMetricProps>) {
+  const {
+    condition: {
+      measure: { metric },
+    },
+  } = props;
+
+  if (metric.type === MetricType.Rating) {
+    return <FailedRatingMetric {...props} />;
+  }
+
+  return <FailedGeneralMetric {...props} />;
+}
+
+function FailedRatingMetric({ condition }: Readonly<FailedMetricProps>) {
+  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: <strong className="sw-body-sm-highlight">{formatMeasure(actual, type)}</strong>,
+          threshold: formatMeasure(error, type),
+        },
+      )}
+    </>
+  );
+}
+
+function FailedGeneralMetric({ condition }: Readonly<FailedMetricProps>) {
+  const {
+    error,
+    measure: { metric },
+  } = condition;
+  const intl = useIntl();
+  const measureFormattingOptions = { decimals: 2, omitExtraDecimalZeros: true };
+
+  return (
+    <>
+      {intl.formatMessage(
+        { id: 'overview.failed_condition.x_required' },
+        {
+          metric: (
+            <>
+              <strong className="sw-body-sm-highlight sw-mr-1">
+                {formatMeasure(
+                  condition.actual,
+                  getShortType(metric.type),
+                  measureFormattingOptions,
+                )}
+              </strong>
+              {getLocalizedMetricName(metric, true)}
+            </>
+          ),
+          threshold: (
+            <>
+              {condition.op === 'GT' ? <>&le;</> : <>&ge;</>}{' '}
+              {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')};
+`;
index 5e7c411c033bcf28586a54bd6794a3281a7f12bb..476221616e71de6820926b6b15505e3fc994b198 100644 (file)
@@ -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 (file)
index 0000000..a841f4d
--- /dev/null
@@ -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<FCProps<typeof BranchQualityGate>> = {}) {
+  return renderComponent(
+    <BranchQualityGate
+      status={Status.ERROR}
+      branchLike={mockPullRequest()}
+      component={mockComponent()}
+      failedConditions={[
+        mockQualityGateStatusConditionEnhanced({
+          actual: '5.0',
+          error: '1.0',
+          metric: MetricKey.new_maintainability_rating,
+          measure: mockMeasureEnhanced({
+            metric: mockMetric({
+              domain: 'Maintainability',
+              key: MetricKey.new_maintainability_rating,
+              name: 'Maintainability rating',
+              type: MetricType.Rating,
+            }),
+          }),
+        }),
+        mockQualityGateStatusConditionEnhanced({
+          actual: '5.0',
+          error: '1.0',
+          metric: MetricKey.new_security_review_rating,
+          measure: mockMeasureEnhanced({
+            metric: mockMetric({
+              domain: 'Security Review',
+              key: MetricKey.new_security_review_rating,
+              name: 'Security Review Rating',
+              type: MetricType.Rating,
+            }),
+          }),
+        }),
+        mockQualityGateStatusConditionEnhanced({
+          actual: '5',
+          error: '1',
+          metric: MetricKey.new_code_smells,
+          measure: mockMeasureEnhanced({
+            metric: mockMetric({
+              domain: 'Maintainability',
+              key: MetricKey.new_code_smells,
+              name: 'Code Smells',
+              type: MetricType.ShortInteger,
+            }),
+          }),
+        }),
+        mockQualityGateStatusConditionEnhanced({
+          actual: '5',
+          error: '10',
+          op: 'up',
+          metric: MetricKey.conditions_to_cover,
+          measure: mockMeasureEnhanced({
+            metric: mockMetric({
+              key: MetricKey.conditions_to_cover,
+              name: 'Conditions to cover',
+              type: MetricType.ShortInteger,
+            }),
+          }),
+        }),
+      ]}
+      {...props}
+    />,
+  );
+}