]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22383 Simplify project overview the Cayc warning
authorMathieu Suen <mathieu.suen@sonarsource.com>
Fri, 14 Jun 2024 13:24:05 +0000 (15:24 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 18 Jun 2024 20:02:41 +0000 (20:02 +0000)
28 files changed:
server/sonar-web/design-system/src/components/Tabs.tsx
server/sonar-web/design-system/src/sonar-aligned/components/Card.tsx
server/sonar-web/src/main/js/apps/overview/branches/ActivityPanel.tsx
server/sonar-web/src/main/js/apps/overview/branches/ApplicationNonCaycProjectWarning.tsx
server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx
server/sonar-web/src/main/js/apps/overview/branches/BranchSummaryStyles.tsx
server/sonar-web/src/main/js/apps/overview/branches/CleanAsYouCodeWarning.tsx
server/sonar-web/src/main/js/apps/overview/branches/FailedConditions.tsx
server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx
server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx
server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx
server/sonar-web/src/main/js/apps/overview/branches/QualityGateConditions.tsx
server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx
server/sonar-web/src/main/js/apps/overview/branches/QualityGateSimplifiedCondition.tsx
server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatus.tsx
server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusTitle.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/branches/SonarLintPromotion.tsx
server/sonar-web/src/main/js/apps/overview/branches/TabsPanel.tsx
server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx
server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateCondition-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateConditions-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGatePanelSection-test.tsx
server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateSimplifiedCondition-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/components/ZeroNewIssuesSimplificationGuide.tsx
server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCardPanel.tsx
server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx
server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/PullRequestOverview-it.tsx
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index a63934bab69c60097ff4cc8e7692fa8077229329..21be271c7800a543f0dae7c418b133f1471b256a 100644 (file)
@@ -114,7 +114,6 @@ const TabButton = styled(BareButton)<{
   ${tw` sw-mb-[-1px]`};
   ${tw`sw-flex sw-items-center`};
   ${(props) => (props.large ? tw`sw-body-md sw-px-6 sw-py-4` : tw`sw-body-sm sw-px-3 sw-py-1`)}
-  ${tw`sw-body-sm`};
   ${tw`sw-font-semibold`};
   ${tw`sw-rounded-t-1`};
 
index a8f7c8ce92949295eff50ecba2a92dd532bb45eb..84e38aa6bac25cbeb028c469bfcb5507fe29e035 100644 (file)
@@ -60,6 +60,20 @@ export const CardWithPrimaryBackground = styled(Card)`
   background-color: ${themeColor('backgroundPrimary')};
 `;
 
+export function InfoCard(props: Readonly<CardProps & { footer?: React.ReactNode }>) {
+  return (
+    <BlueCard>
+      <CardContent>{props.children}</CardContent>
+      {props.footer !== undefined && (
+        <>
+          <BasicSeparator />
+          <CardContent>{props.footer}</CardContent>
+        </>
+      )}
+    </BlueCard>
+  );
+}
+
 const CardStyled = styled.div`
   background-color: ${themeColor('backgroundSecondary')};
   border: ${themeBorder('default', 'projectCardBorder')};
@@ -75,3 +89,12 @@ const LightGreyCardStyled = styled(CardStyled)`
 const GreyCardStyled = styled(CardStyled)`
   border: ${themeBorder('default', 'almCardBorder')};
 `;
+
+const BlueCard = styled.div`
+  ${tw`sw-rounded-1`};
+  border: 1px solid var(--echoes-color-border-default);
+  background: var(--echoes-color-background-info-weak);
+`;
+const CardContent = styled.div`
+  padding: var(--echoes-dimension-space-200);
+`;
index 41abad3aaac26ef2c50ec4ef395ff13f61adb526..98a00733f1c523925d1123d34ea0e43f1ae80e00 100644 (file)
@@ -114,7 +114,7 @@ export function ActivityPanel(props: ActivityPanelProps) {
     <div>
       <h2 className="sw-pt-6 sw-pb-4 sw-body-md-highlight">{translate('overview.activity')}</h2>
 
-      <Card data-test="overview__activity-panel">
+      <Card className="sw-rounded-2" data-test="overview__activity-panel">
         <GraphsHeader graph={graph} metrics={metrics} onUpdateGraph={props.onGraphChange} />
 
         <GraphsHistory
index 454a97744839838562883456e40b014da833c4fd..203e8888a39562536f1b601e20cf2bf71ecc1216 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import { Card, FlagMessage, Link } from 'design-system';
+import { Link } from '@sonarsource/echoes-react';
 import * as React from 'react';
+import { FormattedMessage } from 'react-intl';
 import { getBranchLikeQuery } from '~sonar-aligned/helpers/branch-like';
-import { DocLink } from '../../../helpers/doc-links';
-import { useDocUrl } from '../../../helpers/docs';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { getProjectQueryUrl } from '../../../helpers/urls';
+import { ComponentQualifier } from '../../../sonar-aligned/types/component';
 import { QualityGateStatus } from '../../../types/quality-gates';
 
 interface Props {
@@ -32,28 +31,27 @@ interface Props {
 }
 
 export default function ApplicationNonCaycProjectWarning({ projects }: Props) {
-  const caycUrl = useDocUrl(DocLink.CaYC);
-
   return (
-    <Card className="sw-mt-4 sw-body-sm">
-      <FlagMessage variant="warning">
-        {translateWithParameters(
-          'overview.quality_gate.application.non_cayc.projects_x',
-          projects.length,
-        )}
-      </FlagMessage>
+    <>
+      <p className="sw-font-bold">
+        <FormattedMessage
+          id={`overview.quality_gate.conditions.cayc.warning.title.${ComponentQualifier.Application}`}
+        />
+      </p>
+
+      <p className="sw-my-4">
+        <FormattedMessage
+          id={`overview.quality_gate.conditions.cayc.details.${ComponentQualifier.Application}`}
+        />
+      </p>
 
-      <ul className="sw-mt-4 sw-ml-2 sw-mb-2">
+      <ul className="sw-ml-2 sw-list-disc sw-list-inside">
         {projects.map(({ key, name, branchLike }) => (
-          <li key={key} className="sw-text-ellipsis sw-mb-2" title={name}>
+          <li key={key} className="sw-text-ellipsis" title={name}>
             <Link to={getProjectQueryUrl(key, getBranchLikeQuery(branchLike))}>{name}</Link>
           </li>
         ))}
       </ul>
-      <hr className="sw-my-4" />
-      <div className="sw-m-2 sw-mt-4">
-        <Link to={caycUrl}>{translate('overview.quality_gate.conditions.cayc.link')}</Link>
-      </div>
-    </Card>
+    </>
   );
 }
index 3307b95ed0676597736e7aee4e723d7175448dc4..9b1ef6fe77ec086df21a13ac4de5932679d0cb2e 100644 (file)
@@ -220,7 +220,6 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp
                     )}
                   </>
                 )}
-                <AnalysisStatus className="sw-mt-6" component={component} />
                 <div
                   data-testid="overview__quality-gate-panel"
                   className="sw-flex sw-justify-between sw-items-start sw-my-6"
@@ -228,6 +227,7 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp
                   <QGStatus status={qgStatus} titleSize="extra-large" />
                   <LastAnalysisLabel analysisDate={branch?.analysisDate} />
                 </div>
+                <AnalysisStatus component={component} />
                 <div className="sw-flex sw-flex-col sw-mt-6">
                   <TabsPanel
                     analyses={analyses}
index 0d4095baf8c1f960c3a8c1244a5a4f08d1f1426f..dc44ad87def7541d16232806bcf57c73455c6a5c 100644 (file)
@@ -22,10 +22,12 @@ import styled from '@emotion/styled';
 import { themeColor } from 'design-system/lib';
 
 export const GridContainer = styled.div`
-  --grids-gaps: var(--echoes-dimension-space-500);
+  --column-grids-gaps: var(--echoes-dimension-space-600);
+  --row-grids-gaps: var(--echoes-dimension-space-800);
   display: grid;
   grid-template-columns: repeat(12, minmax(0, 1fr));
-  gap: var(--grids-gaps);
+  row-gap: var(--row-grids-gaps);
+  column-gap: var(--column-grids-gaps);
 `;
 
 export const StyleMeasuresCard = styled.div`
@@ -36,7 +38,7 @@ export const StyleMeasuresCard = styled.div`
     content: '';
     position: absolute;
     top: 0;
-    right: calc(var(--grids-gaps) / -2);
+    right: calc(var(--column-grids-gaps) / -2);
     height: 100%;
     width: 1px;
     background: ${themeColor('pageBlockBorder')};
@@ -45,7 +47,7 @@ export const StyleMeasuresCard = styled.div`
   &:not(:last-child):after {
     content: '';
     position: absolute;
-    bottom: calc(var(--grids-gaps) / -2);
+    bottom: calc(var(--row-grids-gaps) / -2);
     right: 0;
     left: 0px;
     height: 1px;
@@ -62,7 +64,7 @@ export const StyleMeasuresCardRightBorder = styled.div`
     content: '';
     position: absolute;
     top: 0;
-    right: calc(var(--grids-gaps) / -2);
+    right: calc(var(--column-grids-gaps) / -2);
     height: 100%;
     width: 1px;
     background: ${themeColor('pageBlockBorder')};
@@ -76,7 +78,7 @@ export const StyledConditionsCard = styled.div`
     content: '';
     position: absolute;
     top: 0;
-    right: calc(var(--grids-gaps) / -2);
+    right: calc(var(--column-grids-gaps) / -2);
     height: 100%;
     width: 1px;
     background: ${themeColor('pageBlockBorder')};
index b18f457346edaaea110e9c41d6979cddcff7e67c..2d28faa13870e281b2959d7777ec7f6b16ee7988 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import { DiscreetLink, FlagMessage, Link } from 'design-system';
+import { Link } from '@sonarsource/echoes-react';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
-import { DocLink } from '../../../helpers/doc-links';
-import { useDocUrl } from '../../../helpers/docs';
 import { translate } from '../../../helpers/l10n';
 import { getQualityGateUrl } from '../../../helpers/urls';
-import { Component } from '../../../types/types';
+import { Component, QualityGate } from '../../../types/types';
 
 interface Props {
   component: Pick<Component, 'key' | 'qualifier' | 'qualityGate'>;
+  qualityGate?: QualityGate;
 }
 
 export default function CleanAsYouCodeWarning({ component }: Props) {
-  const caycUrl = useDocUrl(DocLink.CaYC);
-
   return (
     <>
-      <FlagMessage variant="warning">
-        {translate('overview.quality_gate.conditions.cayc.warning')}
-      </FlagMessage>
+      <p className="sw-mb-4 sw-font-bold">
+        <FormattedMessage
+          id={`overview.quality_gate.conditions.cayc.warning.title.${component.qualifier}`}
+        />
+      </p>
       {component.qualityGate ? (
-        <p className="sw-my-4">
+        <p>
           <FormattedMessage
             id="overview.quality_gate.conditions.cayc.details_with_link"
             defaultMessage={translate('overview.quality_gate.conditions.cayc.details_with_link')}
             values={{
               link: (
-                <DiscreetLink to={getQualityGateUrl(component.qualityGate.name)}>
+                <Link to={getQualityGateUrl(component.qualityGate.name)}>
                   {translate('overview.quality_gate.conditions.non_cayc.warning.link')}
-                </DiscreetLink>
+                </Link>
               ),
             }}
           />
         </p>
       ) : (
-        <p className="sw-my-4">{translate('overview.quality_gate.conditions.cayc.details')}</p>
+        <p>
+          <FormattedMessage
+            id={`overview.quality_gate.conditions.cayc.details.${component.qualifier}`}
+          />
+        </p>
       )}
-
-      <Link to={caycUrl}>{translate('overview.quality_gate.conditions.cayc.link')}</Link>
     </>
   );
 }
index 223cbf1f8369656b27ee3b9898029631f9814a6d..01f6d5a86a255677fbe297b92741d5aa7535a3ee 100644 (file)
@@ -70,9 +70,7 @@ export default function FailedConditions({
           <CardSeparator />
         </>
       )}
-      {qualityGate?.isBuiltIn && isNewCode && (
-        <ZeroNewIssuesSimplificationGuide qualityGate={qualityGate} />
-      )}
+      {qualityGate && isNewCode && <ZeroNewIssuesSimplificationGuide qualityGate={qualityGate} />}
       <QualityGateConditions
         component={component}
         branchLike={branchLike}
index 6c061fd454b42927130f47fbbd2f35c7867a84e0..a2d0fcacff129410a09baf88cecb55b13d6e443d 100644 (file)
@@ -157,7 +157,7 @@ export default function NewCodeMeasuresPanel(props: Readonly<Props>) {
     <div id={getTabPanelId(CodeScope.New)}>
       {leakPeriod && (
         <span
-          className="sw-body-xs sw-flex sw-items-center sw-mt-8 sw-mr-6"
+          className="sw-body-xs sw-flex sw-items-center sw-mr-6"
           data-spotlight-id="cayc-promotion-2"
         >
           <LightLabel className="sw-mr-1">{translate('overview.new_code')}:</LightLabel>
@@ -168,7 +168,7 @@ export default function NewCodeMeasuresPanel(props: Readonly<Props>) {
       )}
       <GridContainer className=" sw-relative sw-overflow-hidden sw-mt-8 js-summary">
         {!noConditionsAndWarningForNewCode && (
-          <StyledConditionsCard className="sw-row-span-4 sw-col-span-4">
+          <StyledConditionsCard className="sw-row-span-3 sw-col-span-4">
             <QualityGatePanel
               component={component}
               loading={loading}
index 263cf0d60283c6aa0f430f888a59ecd9e22c60fb..ce67b31f40dd741f76f2568b7f6d62a3773deb82 100644 (file)
@@ -41,7 +41,7 @@ import { SoftwareQuality } from '../../../types/clean-code-taxonomy';
 import { isApplication } from '../../../types/component';
 import { IssueStatus } from '../../../types/issues';
 import { QualityGateStatus } from '../../../types/quality-gates';
-import { CaycStatus, Component, MeasureEnhanced, QualityGate } from '../../../types/types';
+import { Component, MeasureEnhanced, QualityGate } from '../../../types/types';
 import MeasuresCard from '../components/MeasuresCard';
 import MeasuresCardNumber from '../components/MeasuresCardNumber';
 import MeasuresCardPercent from '../components/MeasuresCardPercent';
@@ -72,26 +72,7 @@ export default function OverallCodeMeasuresPanel(props: Readonly<OverallCodeMeas
   const securityHotspots = findMeasure(measures, MetricKey.security_hotspots)?.value;
   const securityRating = findMeasure(measures, MetricKey.security_review_rating)?.value;
 
-  const nonCaycProjectsInApp =
-    isApp && qgStatuses
-      ? qgStatuses
-          .filter(({ caycStatus }) => caycStatus === CaycStatus.NonCompliant)
-          .sort(({ name: a }, { name: b }) =>
-            a.localeCompare(b, undefined, { sensitivity: 'base' }),
-          )
-      : [];
-
-  const showCaycWarningInProject =
-    qgStatuses &&
-    qgStatuses.length === 1 &&
-    qgStatuses[0].caycStatus === CaycStatus.NonCompliant &&
-    qualityGate?.actions?.manageConditions &&
-    !isApp;
-
-  const showCaycWarningInApp = nonCaycProjectsInApp.length > 0;
-
-  const noConditionsAndWarningForOverallCode =
-    totalOverallFailedCondition.length === 0 && !showCaycWarningInApp && !showCaycWarningInProject;
+  const noConditionsAndWarningForOverallCode = totalOverallFailedCondition.length === 0;
 
   return (
     <GridContainer
@@ -108,8 +89,6 @@ export default function OverallCodeMeasuresPanel(props: Readonly<OverallCodeMeas
             loading={loading}
             qgStatuses={qgStatuses}
             qualityGate={qualityGate}
-            showCaycWarningInApp={showCaycWarningInApp}
-            showCaycWarningInProject={showCaycWarningInProject ?? false}
             totalFailedConditionLength={totalOverallFailedCondition.length}
           />
         </StyledConditionsCard>
index 185c0d0a6ed23ffd40d176ec7ce65f361a432394..9cad5268f9890f1c786c619033892d8dcee31dc0 100644 (file)
@@ -27,6 +27,7 @@ import {
   getComponentSecurityHotspotsUrl,
 } from '~sonar-aligned/helpers/urls';
 import { MetricKey, MetricType } from '~sonar-aligned/types/metrics';
+import withMetricsContext from '../../../app/components/metrics/withMetricsContext';
 import IssueTypeIcon from '../../../components/icon-mappers/IssueTypeIcon';
 import MeasureIndicator from '../../../components/measure/MeasureIndicator';
 import {
@@ -34,23 +35,23 @@ import {
   isIssueMeasure,
   propsToIssueParams,
 } from '../../../components/shared/utils';
-import { translate } from '../../../helpers/l10n';
-import { isDiffMetric, localizeMetric } from '../../../helpers/measures';
 import { getOperatorLabel } from '../../../helpers/qualityGates';
 import { getComponentDrilldownUrl } from '../../../helpers/urls';
 import { BranchLike } from '../../../types/branch-like';
 import { IssueType } from '../../../types/issues';
 import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
-import { Component, Dict } from '../../../types/types';
+import { Component, Dict, Metric } from '../../../types/types';
+import { getLocalizedMetricNameNoDiffMetric } from '../../quality-gates/utils';
 import { RATING_TO_SEVERITIES_MAPPING } from '../utils';
 
 interface Props {
   branchLike?: BranchLike;
   component: Pick<Component, 'key'>;
   condition: QualityGateStatusConditionEnhanced;
+  metrics: Dict<Metric>;
 }
 
-export default class QualityGateCondition extends React.PureComponent<Props> {
+export class QualityGateCondition extends React.PureComponent<Props> {
   getIssuesUrl = (inNewCodePeriod: boolean, customQuery: Dict<string>) => {
     const query: Dict<string | undefined> = {
       ...DEFAULT_ISSUES_QUERY,
@@ -128,12 +129,8 @@ export default class QualityGateCondition extends React.PureComponent<Props> {
     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);
+    const subText = getLocalizedMetricNameNoDiffMetric(metric, this.props.metrics);
 
     if (metric.type !== MetricType.Rating) {
       const actual = (condition.period ? measure.period?.value : measure.value) as string;
@@ -179,3 +176,5 @@ export default class QualityGateCondition extends React.PureComponent<Props> {
     );
   }
 }
+
+export default withMetricsContext(QualityGateCondition);
index 51d9dd8582037cbb3c0bae9c7b2b783061eb5484..3b9fc353c0b096976a0bdb64e5f92483cda59280 100644 (file)
@@ -72,8 +72,8 @@ export function QualityGateConditions(props: Readonly<QualityGateConditionsProps
   }
 
   return (
-    <ul id="overview-quality-gate-conditions-list" className="sw-mb-2">
-      {renderConditions.map((condition) => (
+    <ul id="overview-quality-gate-conditions-list">
+      {renderConditions.map((condition, idx) => (
         <div key={condition.measure.metric.key}>
           {isSimplifiedCondition(condition) ? (
             <QualityGateSimplifiedCondition
@@ -88,7 +88,7 @@ export function QualityGateConditions(props: Readonly<QualityGateConditionsProps
               condition={condition}
             />
           )}
-          <CardSeparator />
+          {idx !== renderConditions.length - 1 && <CardSeparator />}
         </div>
       ))}
       {renderCollapsed && (
index fb1dd98d32170da0d69e47f565f3358e715841ec..7b955a46a0dc5fada3fd48cf1c3640c9a7370aed 100644 (file)
  * 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, CardSeparator, Spinner, TextError } from 'design-system';
+import styled from '@emotion/styled';
+import { LinkStandalone, Spinner } from '@sonarsource/echoes-react';
+import { CardSeparator, InfoCard, TextError } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
 import { ComponentQualifier } from '~sonar-aligned/types/component';
+import { DocLink } from '../../../helpers/doc-links';
+import { useDocUrl } from '../../../helpers/docs';
 import { translate } from '../../../helpers/l10n';
 import { isDiffMetric } from '../../../helpers/measures';
 import { isApplication } from '../../../types/component';
@@ -55,6 +59,8 @@ export function QualityGatePanel(props: QualityGatePanelProps) {
     showCaycWarningInApp = false,
   } = props;
 
+  const caycUrl = useDocUrl(DocLink.CaYC);
+
   if (qgStatuses === undefined) {
     return null;
   }
@@ -76,9 +82,9 @@ export function QualityGatePanel(props: QualityGatePanelProps) {
     qgStatuses.some((p) => Boolean(p.ignoredConditions));
 
   return (
-    <div data-testid="overview__quality-gate-panel-conditions">
-      <div>
-        <Spinner loading={loading}>
+    <Spinner isLoading={loading}>
+      <Column data-testid="overview__quality-gate-panel-conditions">
+        <Conditions>
           {showIgnoredConditionWarning && isNewCode && <IgnoredConditionWarning />}
 
           {isApp && (
@@ -120,20 +126,49 @@ export function QualityGatePanel(props: QualityGatePanelProps) {
               })}
             </div>
           )}
-        </Spinner>
-      </div>
-
-      {showCaycWarningInApp && <ApplicationNonCaycProjectWarning projects={nonCaycProjectsInApp} />}
+        </Conditions>
 
-      {showCaycWarningInProject && (
-        <Card className="sw-mt-4 sw-body-sm">
-          <CleanAsYouCodeWarning component={component} />
-        </Card>
-      )}
+        {showCaycWarningInApp && (
+          <InfoCard
+            className="sw-body-sm"
+            footer={
+              <LinkStandalone to={caycUrl}>
+                <FormattedMessage id="overview.quality_gate.conditions.cayc.link" />
+              </LinkStandalone>
+            }
+          >
+            <ApplicationNonCaycProjectWarning projects={nonCaycProjectsInApp} />
+          </InfoCard>
+        )}
 
-      <SonarLintPromotion qgConditions={qgStatuses?.flatMap((qg) => qg.failedConditions)} />
-    </div>
+        {showCaycWarningInProject && (
+          <InfoCard
+            className="sw-body-sm"
+            footer={
+              <LinkStandalone to={caycUrl}>
+                <FormattedMessage id="overview.quality_gate.conditions.cayc.link" />
+              </LinkStandalone>
+            }
+          >
+            <CleanAsYouCodeWarning component={component} />
+          </InfoCard>
+        )}
+        <SonarLintPromotion qgConditions={qgStatuses?.flatMap((qg) => qg.failedConditions)} />
+      </Column>
+    </Spinner>
   );
 }
 
 export default React.memo(QualityGatePanel);
+
+const Column = styled.div`
+  display: flex;
+  flex-direction: column;
+  gap: var(--echoes-dimension-space-400);
+`;
+
+const Conditions = styled.div`
+  &:empty {
+    display: contents;
+  }
+`;
index 4a12c8adeb850cfbc37a74819e36b41f9d89e362..4cb93fdb7efa79be98b4a36df9f1a9452aa474df 100644 (file)
@@ -23,12 +23,12 @@ import { getBranchLikeQuery } from '~sonar-aligned/helpers/branch-like';
 import { formatMeasure } from '~sonar-aligned/helpers/measures';
 import { getComponentIssuesUrl } from '~sonar-aligned/helpers/urls';
 import { MetricKey, MetricType } from '~sonar-aligned/types/metrics';
+import { useMetrics } from '../../../app/components/metrics/withMetricsContext';
 import { propsToIssueParams } from '../../../components/shared/utils';
-import { translate } from '../../../helpers/l10n';
-import { isDiffMetric, localizeMetric } from '../../../helpers/measures';
 import { BranchLike } from '../../../types/branch-like';
 import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
 import { Component } from '../../../types/types';
+import { getLocalizedMetricNameNoDiffMetric } from '../../quality-gates/utils';
 
 interface Props {
   branchLike?: BranchLike;
@@ -41,15 +41,12 @@ export default function QualityGateSimplifiedCondition({
   component,
   condition,
 }: Readonly<Props>) {
+  const metrics = useMetrics();
   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);
+    const subText = getLocalizedMetricNameNoDiffMetric(metric, metrics);
 
     return subText;
   };
index 65e3b3d2eb31d22a575800a95af4319ad61fcee0..04715c9965f0cbe0744c89007ac95f7e6ec1ccde 100644 (file)
@@ -37,7 +37,7 @@ export default function QualityGateStatus(props: Readonly<Props>) {
       <QualityGateIndicator size="xl" status={status} />
       <div className="sw-flex sw-flex-col sw-ml-2 sw-justify-around">
         <div className="sw-flex sw-items-center">
-          <Note>{translate('overview.quality_gate.status')}</Note>
+          <Note>{translate('overview.quality_gate')}</Note>
           <HelpTooltip
             className="sw-ml-2"
             overlay={<div>{translate('overview.quality_gate.help')}</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
deleted file mode 100644 (file)
index e8328b7..0000000
+++ /dev/null
@@ -1,39 +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, LightGreyCardTitle, PageTitle } from 'design-system';
-import React from 'react';
-import HelpTooltip from '~sonar-aligned/components/controls/HelpTooltip';
-import { translate } from '../../../helpers/l10n';
-
-export function QualityGateStatusTitle() {
-  return (
-    <LightGreyCardTitle>
-      <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>
-    </LightGreyCardTitle>
-  );
-}
index 09e34fa2359ca7a6b246a145aaff7f6845f7a8a2..79bc8d400e713744b32f75fdb4f10e8c5af2194f 100644 (file)
@@ -17,7 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { Card, DiscreetLink } from 'design-system';
+import { DiscreetLink, InfoCard } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
 import { MetricKey } from '~sonar-aligned/types/metrics';
@@ -56,7 +56,7 @@ export function SonarLintPromotion({ currentUser, qgConditions }: SonarLintPromo
     return null;
   }
   return (
-    <Card className="it__overview__sonarlint-promotion sw-my-4 sw-body-sm">
+    <InfoCard className="it__overview__sonarlint-promotion sw-body-sm">
       <FormattedMessage
         id="overview.fix_failed_conditions_with_sonarlint"
         defaultMessage={translate('overview.fix_failed_conditions_with_sonarlint')}
@@ -79,7 +79,7 @@ export function SonarLintPromotion({ currentUser, qgConditions }: SonarLintPromo
           ),
         }}
       />
-    </Card>
+    </InfoCard>
   );
 }
 
index 9e3fc03459fb6ebe909ae6f2b7cb90b20d3d703c..2dff299120eca0f889b88f8011066d33d1242f66 100644 (file)
@@ -106,11 +106,7 @@ export default function TabsPanel(props: React.PropsWithChildren<MeasuresPanelPr
   ];
 
   return (
-    <div
-      className="sw-mt-3"
-      data-testid="overview__measures-panel"
-      data-spotlight-id="cayc-promotion-1"
-    >
+    <div data-testid="overview__measures-panel" data-spotlight-id="cayc-promotion-1">
       {loading ? (
         <div>
           <Spinner isLoading={loading} />
@@ -145,7 +141,7 @@ export default function TabsPanel(props: React.PropsWithChildren<MeasuresPanelPr
             />
           </div>
 
-          <Card className="sw-rounded-b-2 sw-rounded-t-0 sw-border-t-0">
+          <Card className="sw-rounded-b-2 sw-rounded-t-0 sw-border-t-0 sw-pt-8 sw-pb-12 sw-px-6">
             {component.qualifier === ComponentQualifier.Application && component.needIssueSync && (
               <FlagMessage className="sw-mt-4" variant="info">
                 <span>
index 8d260d5fe9334d3f1d1199fa3454f62b40055dbd..f502327d88d07694fdc5ce85788e237501008806 100644 (file)
@@ -143,7 +143,7 @@ describe('project overview', () => {
     // QG panel
     expect(screen.getByText('metric.level.OK')).toBeInTheDocument();
     expect(
-      screen.queryByText('overview.quality_gate.conditions.cayc.warning'),
+      screen.queryByText('overview.quality_gate.conditions.cayc.warning.title.TRK'),
     ).not.toBeInTheDocument();
 
     // Measures panel
@@ -171,7 +171,7 @@ describe('project overview', () => {
 
     expect(await screen.findByText('metric.level.OK')).toBeInTheDocument();
     expect(
-      screen.queryByText('overview.quality_gate.conditions.cayc.warning'),
+      screen.queryByText('overview.quality_gate.conditions.cayc.warning.title.TRK'),
     ).not.toBeInTheDocument();
   });
 
@@ -188,7 +188,7 @@ describe('project overview', () => {
 
     await screen.findByText('metric.level.OK');
     expect(
-      await screen.findByText('overview.quality_gate.conditions.cayc.warning'),
+      await screen.findByText('overview.quality_gate.conditions.cayc.warning.title.TRK'),
     ).toBeInTheDocument();
   });
 
@@ -255,7 +255,7 @@ describe('project overview', () => {
     expect(screen.getAllByText(/overview.quality_gate.required_x/)).toHaveLength(3);
     expect(
       screen.getByRole('link', {
-        name: '1 1 metric.new_security_hotspots_reviewed.name quality_gates.operator.GT 2',
+        name: '1 1 new_security_hotspots_reviewed quality_gates.operator.GT 2',
       }),
     ).toHaveAttribute('href', '/security_hotspots?id=foo&inNewCodePeriod=true');
   });
@@ -615,13 +615,15 @@ describe('application overview', () => {
       }).get(),
     ).toBeInTheDocument();
     expect(byText(/quality_gates.conditions.x_conditions_failed/).get()).toBeInTheDocument();
-    expect(byText('1 metric.new_violations.name').get()).toBeInTheDocument();
+    expect(
+      byRole('link', { name: '1 1 new_violations quality_gates.operator.GT 0' }).get(),
+    ).toBeInTheDocument();
   });
 
   it("should show projects that don't have a compliant quality gate", async () => {
     renderBranchOverview({ component });
     expect(
-      await screen.findByText('overview.quality_gate.application.non_cayc.projects_x.3'),
+      await screen.findByText('overview.quality_gate.conditions.cayc.details.APP'),
     ).toBeInTheDocument();
     expect(screen.getByText('first project')).toBeInTheDocument();
     expect(screen.queryByText('second project')).not.toBeInTheDocument();
@@ -667,7 +669,7 @@ it.each([
     renderBranchOverview();
 
     // wait for loading
-    await screen.findByText('overview.quality_gate.status');
+    await screen.findByText('overview.quality_gate');
 
     expect(screen.queryByText('overview.project.next_steps.set_up_ci') === null).toBe(expected);
   },
@@ -761,7 +763,7 @@ it.each([
 
     await user.click(await ui.overallCodeButton.find());
 
-    expect(await byText('overview.quality_gate.status').find()).toBeInTheDocument();
+    expect(await byText('overview.quality_gate').find()).toBeInTheDocument();
 
     await waitFor(() =>
       expect(
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
deleted file mode 100644 (file)
index baebe48..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 { MetricKey, MetricType } from '~sonar-aligned/types/metrics';
-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 { 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
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} />,
-  );
-}
index af61e9ccd75da606205b47d3357c9c4744401922..ad94d7c022a232c3d8aec74e27bfb069fabeea89 100644 (file)
@@ -80,19 +80,6 @@ const qgStatus = mockQualityGateStatus({
   status: 'ERROR' as Status,
 });
 
-it('should render correctly for an application for new code section', async () => {
-  renderQualityGatePanelSection();
-
-  expect(await screen.findByText('metric.new_coverage.name')).toBeInTheDocument();
-  expect(screen.getByText('metric.new_violations.name')).toBeInTheDocument();
-});
-
-it('should render correctly for an application for overall code section', async () => {
-  renderQualityGatePanelSection({ isNewCode: false });
-
-  expect(await screen.findByText('metric.security_hotspots.name')).toBeInTheDocument();
-});
-
 it('should render correctly for a project with 1 new code condition', () => {
   renderQualityGatePanelSection({
     isApplication: false,
@@ -110,7 +97,6 @@ it('should render correctly 0 New issues onboarding', async () => {
     qualityGate: mockQualityGate({ isBuiltIn: true }),
   });
 
-  expect(screen.queryByText('quality_gates.conditions.new_code_1')).not.toBeInTheDocument();
   expect(await byRole('alertdialog').find()).toBeInTheDocument();
 });
 
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
deleted file mode 100644 (file)
index f02ac27..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 { MetricKey, MetricType } from '~sonar-aligned/types/metrics';
-import { mockQualityGateStatusConditionEnhanced } from '../../../../helpers/mocks/quality-gates';
-import { mockMetric } from '../../../../helpers/testMocks';
-import { renderComponent } from '../../../../helpers/testReactTestingUtils';
-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 } : {}),
-  });
-}
index 67312f41906bca06a574e4fde0a83e9a604290e8..2c3bdb0743e9e1941325906a6f98d4e6be9ce0a6 100644 (file)
@@ -34,6 +34,7 @@ interface Props {
 export default function ZeroNewIssuesSimplificationGuide({ qualityGate }: Readonly<Props>) {
   const { currentUser, updateDismissedNotices } = React.useContext(CurrentUserContext);
   const shouldRun =
+    Boolean(qualityGate.isBuiltIn) &&
     currentUser.isLoggedIn &&
     !currentUser.dismissedNotices[NoticeType.OVERVIEW_ZERO_NEW_ISSUES_SIMPLIFICATION];
 
index 14bb087b4e5ab34cc4d536b9f6e7de58e3b7fa41..13aeb32b1b6a0b51054150595d98d8e832f8f7cc 100644 (file)
@@ -44,7 +44,7 @@ import { getBranchLikeQuery } from '../../../sonar-aligned/helpers/branch-like';
 import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
 import { PullRequest } from '../../../types/branch-like';
 import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
-import { Component, MeasureEnhanced } from '../../../types/types';
+import { Component, MeasureEnhanced, QualityGate } from '../../../types/types';
 import {
   GridContainer,
   StyleMeasuresCard,
@@ -67,10 +67,11 @@ interface Props {
   conditions: QualityGateStatusConditionEnhanced[];
   measures: MeasureEnhanced[];
   pullRequest: PullRequest;
+  qualityGate?: QualityGate;
 }
 
 export default function MeasuresCardPanel(props: React.PropsWithChildren<Props>) {
-  const { pullRequest, component, measures, conditions } = props;
+  const { pullRequest, component, measures, conditions, qualityGate } = props;
 
   const newSecurityHotspots = getLeakValue(
     findMeasure(measures, MetricKey.new_security_hotspots),
@@ -100,9 +101,9 @@ export default function MeasuresCardPanel(props: React.PropsWithChildren<Props>)
   const totalFailedConditions = conditions.filter((condition) => condition.level === Status.ERROR);
 
   return (
-    <Card>
+    <Card className="sw-py-8 sw-px-6">
       <GridContainer
-        className={classNames('sw-relative sw-overflow-hidden sw-mt-8 js-summary', {
+        className={classNames('sw-relative sw-overflow-hidden js-summary', {
           'sw-grid-cols-3': totalFailedConditions.length === 0,
           'sw-grid-cols-4': totalFailedConditions.length > 0,
         })}
@@ -110,6 +111,7 @@ export default function MeasuresCardPanel(props: React.PropsWithChildren<Props>)
         {totalFailedConditions.length > 0 && (
           <StyledConditionsCard className="sw-row-span-3">
             <FailedConditions
+              qualityGate={qualityGate}
               branchLike={pullRequest}
               failedConditions={totalFailedConditions}
               isNewCode
@@ -121,9 +123,6 @@ export default function MeasuresCardPanel(props: React.PropsWithChildren<Props>)
           <IssueMeasuresCardInner
             header={intl.formatMessage({ id: 'overview.new_issues' })}
             data-testid={`overview__measures-${MetricKey.new_violations}`}
-            data-guiding-id={
-              isIssuesConditionFailed ? 'overviewZeroNewIssuesSimplification' : undefined
-            }
             metric={MetricKey.new_violations}
             value={formatMeasure(issuesCount, MetricType.ShortInteger)}
             url={issuesUrl}
index e5778e1bd4897491b4ebc0ebd28596260204ca9a..13ac9822b9700fd1b7604084b042271a79ef604d 100644 (file)
@@ -32,7 +32,6 @@ import QGStatus from '../branches/QualityGateStatus';
 import { AnalysisStatus } from '../components/AnalysisStatus';
 import IgnoredConditionWarning from '../components/IgnoredConditionWarning';
 import LastAnalysisLabel from '../components/LastAnalysisLabel';
-import ZeroNewIssuesSimplificationGuide from '../components/ZeroNewIssuesSimplificationGuide';
 import '../styles.css';
 import { PR_METRICS } from '../utils';
 import MeasuresCardPanel from './MeasuresCardPanel';
@@ -111,11 +110,10 @@ export default function PullRequestOverview(props: Readonly<Readonly<Props>>) {
             pullRequest={pullRequest}
             component={component}
             conditions={enhancedConditions}
+            qualityGate={qualityGate}
             measures={measures}
           />
 
-          {qualityGate?.isBuiltIn && <ZeroNewIssuesSimplificationGuide qualityGate={qualityGate} />}
-
           <SonarLintAd status={status} />
         </div>
       </PageContentFontWrapper>
index 4ff9d9a9378f27493a5f04473102608c55f80b37..8e89778655188075d450c84587dfa534f14a63c6 100644 (file)
@@ -20,7 +20,7 @@
 import { screen, waitFor } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import * as React from 'react';
-import { byLabelText, byRole } from '~sonar-aligned/helpers/testSelector';
+import { byRole, byText } from '~sonar-aligned/helpers/testSelector';
 import { ComponentQualifier } from '~sonar-aligned/types/component';
 import { MetricKey, MetricType } from '~sonar-aligned/types/metrics';
 import BranchesServiceMock from '../../../../api/mocks/BranchesServiceMock';
@@ -142,7 +142,7 @@ it('should render links correctly', async () => {
   renderPullRequestOverview();
 
   await waitFor(async () => expect(await screen.findByText('metric.level.OK')).toBeInTheDocument());
-  expect(screen.getByLabelText('overview.quality_gate_x.overview.gate.OK')).toBeInTheDocument();
+  expect(screen.getByText('metric.level.OK')).toBeInTheDocument();
 
   expect(
     byRole('link', {
@@ -181,8 +181,7 @@ it('should render correctly for a passed QG', async () => {
   });
   renderPullRequestOverview();
 
-  await waitFor(async () => expect(await screen.findByText('metric.level.OK')).toBeInTheDocument());
-  expect(screen.getByLabelText('overview.quality_gate_x.overview.gate.OK')).toBeInTheDocument();
+  expect(await screen.findByText('metric.level.OK')).toBeInTheDocument();
 
   expect(screen.getByText('metric.new_lines.name')).toBeInTheDocument();
   expect(
@@ -230,20 +229,16 @@ it('should render correctly for a failed QG', async () => {
   });
   renderPullRequestOverview();
 
-  await waitFor(async () =>
-    expect(
-      await byLabelText('overview.quality_gate_x.overview.gate.ERROR').find(),
-    ).toBeInTheDocument(),
-  );
+  expect(await byText('metric.level.ERROR').find()).toBeInTheDocument();
 
   expect(
     byRole('link', {
-      name: 'overview.measures.failed_badge overview.failed_condition.x_required 10.0% duplicated_lines≤ 1.0%',
+      name: '1 1 new_bugs quality_gates.operator.GT 3',
     }).get(),
   ).toBeInTheDocument();
   expect(
     byRole('link', {
-      name: 'overview.measures.failed_badge overview.failed_condition.x_required 10 new_bugs≤ 3',
+      name: '1.0% new_coverage quality_gates.operator.GT 2.0%',
     }).get(),
   ).toBeInTheDocument();
 });
@@ -296,9 +291,7 @@ it('should render correctly 0 New issues onboarding', async () => {
 
   renderPullRequestOverview();
 
-  expect(
-    await byLabelText('overview.quality_gate_x.overview.gate.ERROR').find(),
-  ).toBeInTheDocument();
+  expect(await byText('metric.level.ERROR').find()).toBeInTheDocument();
   expect(await byRole('alertdialog').find()).toBeInTheDocument();
 });
 
@@ -325,11 +318,7 @@ it('should not render 0 New issues onboarding when user dismissed it', async ()
     }),
   );
 
-  await waitFor(async () =>
-    expect(
-      await byLabelText('overview.quality_gate_x.overview.gate.ERROR').find(),
-    ).toBeInTheDocument(),
-  );
+  expect(await byText('metric.level.ERROR').find()).toBeInTheDocument();
 
   expect(await byRole('alertdialog').query()).not.toBeInTheDocument();
 });
index d23ae392df9f453ffdf0b28326ed3fdcaaf54631..1d1609f85c1dab6545efea3b7b4232de06ea696e 100644 (file)
@@ -3903,7 +3903,6 @@ overview.pull_request.fixed_issues.disclaimer=Only issues fixed on the files mod
 overview.pull_request.fixed_issues.disclaimer.2=When the pull request and the target branch are not synchronized, issues introduced on the target branch may be incorrectly considered fixed by the pull request. Rebasing the pull request would give an updated value.
 overview.accepted_issues=Accepted issues
 overview.accepted_issues.help=Valid issues that were not fixed
-overview.quality_gate.status=Quality Gate Status
 overview.quality_gate=Quality Gate
 overview.quality_gate_x=Quality Gate: {0}
 overview.quality_gate.help=A Quality Gate is a set of measure-based Boolean conditions. It helps you know immediately whether your project is production-ready. If your current status is not Passed, you'll see which measures caused the problem and the values required to pass.
@@ -3913,16 +3912,17 @@ overview.you_should_define_quality_gate=You should define a quality gate on this
 overview.quality_gate.ignored_conditions=Some Quality Gate conditions on New Code were ignored because of the small number of New Lines
 overview.quality_gate.ignored_conditions.tooltip=At the start of a new code period, if very few lines have been added or modified, it might be difficult to reach the desired level of code coverage or duplications. To prevent Quality Gate failure when there's little that can be done about it, Quality Gate conditions about duplications in new code and coverage on new code are ignored until the number of new lines is at least 20. An administrator can disable this in the general settings.
 overview.quality_gate.conditions_on_new_code=Only conditions on new code that are defined in the Quality Gate are checked. See the {link} associated to the project for details.
-overview.quality_gate.conditions.cayc.warning=The quality gate used by this project does not comply with Clean as You Code.
-overview.quality_gate.conditions.cayc.details=Fixing this quality gate will help you achieve a Clean Code state.
-overview.quality_gate.conditions.cayc.details_with_link=Fixing {link} will help you achieve a Clean Code state.
-overview.quality_gate.conditions.non_cayc.warning.link=this quality gate
-overview.quality_gate.conditions.cayc.link=Learn why
+overview.quality_gate.conditions.cayc.warning.title.APP=Some projects are not ready for Clean as You Code
+overview.quality_gate.conditions.cayc.warning.title.TRK=This project is not ready for Clean as You Code
+overview.quality_gate.conditions.cayc.details.APP=The quality gate used by these projects can be improved to enable Clean as You Code:
+overview.quality_gate.conditions.cayc.details.TRK=The quality gate used by this project can be improved to enable Clean as You Code.
+overview.quality_gate.conditions.cayc.details_with_link=The {link} used by this project can be improved to enable Clean as You Code.
+overview.quality_gate.conditions.non_cayc.warning.link=quality gate
+overview.quality_gate.conditions.cayc.link=Learn more
 overview.quality_gates.conditions.condition_simplification_tour.title=One condition, zero issues
 overview.quality_gates.conditions.condition_simplification_tour.content1=A new condition was introduced in {link} to ensure that new code has no issues.
 overview.quality_gates.conditions.condition_simplification_tour.content1.link={0} quality gate
 overview.quality_gates.conditions.condition_simplification_tour.content2=Starting now, every issue in new code must be resolved for a project to pass this quality gate.
-overview.quality_gate.application.non_cayc.projects_x={0} project(s) in this application use a Quality Gate that does not comply with Clean as You Code
 overview.quality_gate.show_project_conditions_x=Show failed conditions for project {0}
 overview.quality_gate.hide_project_conditions_x=Hide failed conditions for project {0}
 overview.quality_gate.coverage=Coverage