aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMathieu Suen <mathieu.suen@sonarsource.com>2024-06-14 15:24:05 +0200
committersonartech <sonartech@sonarsource.com>2024-06-18 20:02:41 +0000
commit0951fc99d35ff871c4a2a572772ab17a364f8064 (patch)
tree4a6d5ef8c83aceb2811ef794f4d8875b7f2e2d14
parent50ba3a3998f109b8540cd9485cbc19b160e85d59 (diff)
downloadsonarqube-0951fc99d35ff871c4a2a572772ab17a364f8064.tar.gz
sonarqube-0951fc99d35ff871c4a2a572772ab17a364f8064.zip
SONAR-22383 Simplify project overview the Cayc warning
-rw-r--r--server/sonar-web/design-system/src/components/Tabs.tsx1
-rw-r--r--server/sonar-web/design-system/src/sonar-aligned/components/Card.tsx23
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/ActivityPanel.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/ApplicationNonCaycProjectWarning.tsx38
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/BranchSummaryStyles.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/CleanAsYouCodeWarning.tsx31
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/FailedConditions.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx25
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/QualityGateConditions.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx65
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/QualityGateSimplifiedCondition.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatus.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusTitle.tsx39
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/SonarLintPromotion.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/TabsPanel.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx18
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateCondition-test.tsx97
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateConditions-test.tsx72
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGatePanelSection-test.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateSimplifiedCondition-test.tsx67
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/ZeroNewIssuesSimplificationGuide.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCardPanel.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/PullRequestOverview-it.tsx27
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties14
28 files changed, 176 insertions, 449 deletions
diff --git a/server/sonar-web/design-system/src/components/Tabs.tsx b/server/sonar-web/design-system/src/components/Tabs.tsx
index a63934bab69..21be271c780 100644
--- a/server/sonar-web/design-system/src/components/Tabs.tsx
+++ b/server/sonar-web/design-system/src/components/Tabs.tsx
@@ -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`};
diff --git a/server/sonar-web/design-system/src/sonar-aligned/components/Card.tsx b/server/sonar-web/design-system/src/sonar-aligned/components/Card.tsx
index a8f7c8ce929..84e38aa6bac 100644
--- a/server/sonar-web/design-system/src/sonar-aligned/components/Card.tsx
+++ b/server/sonar-web/design-system/src/sonar-aligned/components/Card.tsx
@@ -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);
+`;
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/ActivityPanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/ActivityPanel.tsx
index 41abad3aaac..98a00733f1c 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/ActivityPanel.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/ActivityPanel.tsx
@@ -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
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/ApplicationNonCaycProjectWarning.tsx b/server/sonar-web/src/main/js/apps/overview/branches/ApplicationNonCaycProjectWarning.tsx
index 454a9774483..203e8888a39 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/ApplicationNonCaycProjectWarning.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/ApplicationNonCaycProjectWarning.tsx
@@ -18,13 +18,12 @@
* 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>
+ </>
);
}
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx
index 3307b95ed06..9b1ef6fe77e 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx
@@ -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}
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/BranchSummaryStyles.tsx b/server/sonar-web/src/main/js/apps/overview/branches/BranchSummaryStyles.tsx
index 0d4095baf8c..dc44ad87def 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/BranchSummaryStyles.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/BranchSummaryStyles.tsx
@@ -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')};
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/CleanAsYouCodeWarning.tsx b/server/sonar-web/src/main/js/apps/overview/branches/CleanAsYouCodeWarning.tsx
index b18f457346e..2d28faa1387 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/CleanAsYouCodeWarning.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/CleanAsYouCodeWarning.tsx
@@ -18,46 +18,47 @@
* 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>
</>
);
}
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/FailedConditions.tsx b/server/sonar-web/src/main/js/apps/overview/branches/FailedConditions.tsx
index 223cbf1f836..01f6d5a86a2 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/FailedConditions.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/FailedConditions.tsx
@@ -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}
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx
index 6c061fd454b..a2d0fcacff1 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx
@@ -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}
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx
index 263cf0d6028..ce67b31f40d 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx
@@ -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>
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
index 185c0d0a6ed..9cad5268f98 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx
@@ -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);
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateConditions.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateConditions.tsx
index 51d9dd85820..3b9fc353c0b 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateConditions.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateConditions.tsx
@@ -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 && (
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx
index fb1dd98d321..7b955a46a0d 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx
@@ -17,10 +17,14 @@
* 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;
+ }
+`;
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
index 4a12c8adeb8..4cb93fdb7ef 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateSimplifiedCondition.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateSimplifiedCondition.tsx
@@ -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;
};
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatus.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatus.tsx
index 65e3b3d2eb3..04715c9965f 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatus.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatus.tsx
@@ -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
index e8328b75b20..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusTitle.tsx
+++ /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>
- );
-}
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
index 09e34fa2359..79bc8d400e7 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/SonarLintPromotion.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/SonarLintPromotion.tsx
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { 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>
);
}
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/TabsPanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/TabsPanel.tsx
index 9e3fc03459f..2dff299120e 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/TabsPanel.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/TabsPanel.tsx
@@ -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>
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx
index 8d260d5fe93..f502327d88d 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx
@@ -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
index baebe48e457..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateCondition-test.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { screen } from '@testing-library/react';
-import * as React from 'react';
-import { 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
index 46697d0246d..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateConditions-test.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import * as React from 'react';
-import { mockComponent } from '../../../../helpers/mocks/component';
-import { mockQualityGateStatusConditionEnhanced } from '../../../../helpers/mocks/quality-gates';
-import { mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks';
-import { renderComponent } from '../../../../helpers/testReactTestingUtils';
-import { QualityGateStatusConditionEnhanced } from '../../../../types/quality-gates';
-import { QualityGateConditions, QualityGateConditionsProps } from '../QualityGateConditions';
-
-const ALL_CONDITIONS = 10;
-const HALF_CONDITIONS = 5;
-
-it('should render correctly', async () => {
- renderQualityGateConditions();
- expect(await screen.findAllByText(/.*metric..+.name.*/)).toHaveLength(ALL_CONDITIONS);
-
- expect(await screen.findAllByText('quality_gates.operator', { exact: false })).toHaveLength(
- ALL_CONDITIONS,
- );
-});
-
-it('should be collapsible', async () => {
- renderQualityGateConditions({ collapsible: true });
- const user = userEvent.setup();
-
- expect(await screen.findAllByText(/.*metric..+.name.*/)).toHaveLength(HALF_CONDITIONS);
- expect(await screen.findAllByText('quality_gates.operator', { exact: false })).toHaveLength(
- HALF_CONDITIONS,
- );
-
- await user.click(screen.getByRole('link', { name: 'show_more' }));
-
- expect(await screen.findAllByText(/.*metric..+.name.*/)).toHaveLength(ALL_CONDITIONS);
- expect(await screen.findAllByText('quality_gates.operator', { exact: false })).toHaveLength(
- ALL_CONDITIONS,
- );
-});
-
-function renderQualityGateConditions(props: Partial<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__/QualityGatePanelSection-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGatePanelSection-test.tsx
index af61e9ccd75..ad94d7c022a 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGatePanelSection-test.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGatePanelSection-test.tsx
@@ -80,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
index f02ac27c32b..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGateSimplifiedCondition-test.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { screen } from '@testing-library/react';
-import React from 'react';
-import { 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 } : {}),
- });
-}
diff --git a/server/sonar-web/src/main/js/apps/overview/components/ZeroNewIssuesSimplificationGuide.tsx b/server/sonar-web/src/main/js/apps/overview/components/ZeroNewIssuesSimplificationGuide.tsx
index 67312f41906..2c3bdb0743e 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/ZeroNewIssuesSimplificationGuide.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/ZeroNewIssuesSimplificationGuide.tsx
@@ -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];
diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCardPanel.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCardPanel.tsx
index 14bb087b4e5..13aeb32b1b6 100644
--- a/server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCardPanel.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/MeasuresCardPanel.tsx
@@ -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}
diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx
index e5778e1bd48..13ac9822b97 100644
--- a/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx
@@ -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>
diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/PullRequestOverview-it.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/PullRequestOverview-it.tsx
index 4ff9d9a9378..8e897786551 100644
--- a/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/PullRequestOverview-it.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/PullRequestOverview-it.tsx
@@ -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();
});
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index d23ae392df9..1d1609f85c1 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -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