aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorRevanshu Paliwal <revanshu.paliwal@sonarsource.com>2024-06-13 14:13:40 +0200
committersonartech <sonartech@sonarsource.com>2024-06-18 20:02:41 +0000
commit5ff3751aa69b21ac0269eb9bc1b54625d42ecc08 (patch)
treea4e9839ccc8f091d7ab721312598acb6c19f365b /server
parent0a404611b8d3db1c45c0fd64927ca8d0c42458e2 (diff)
downloadsonarqube-5ff3751aa69b21ac0269eb9bc1b54625d42ecc08.tar.gz
sonarqube-5ff3751aa69b21ac0269eb9bc1b54625d42ecc08.zip
SONAR-22385 Updating layout for new and overall code inside project overview
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/BranchSummaryStyles.tsx69
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/FailedConditions.tsx80
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelPercentCards.tsx91
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx286
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx117
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/QualityGateConditions.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx103
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanelSection.tsx130
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusPassedView.tsx31
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx4
-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/components/MeasuresCard.tsx12
14 files changed, 565 insertions, 402 deletions
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 cce94e2a327..3307b95ed06 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
@@ -135,7 +135,6 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp
<AnalysisMissingInfoMessage
qualifier={component.qualifier}
hide={isPortfolioLike(component.qualifier)}
- className="sw-mt-6"
/>
);
@@ -222,7 +221,10 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp
</>
)}
<AnalysisStatus className="sw-mt-6" component={component} />
- <div className="sw-flex sw-justify-between sw-items-start sw-my-6">
+ <div
+ data-testid="overview__quality-gate-panel"
+ className="sw-flex sw-justify-between sw-items-start sw-my-6"
+ >
<QGStatus status={qgStatus} titleSize="extra-large" />
<LastAnalysisLabel analysisDate={branch?.analysisDate} />
</div>
@@ -243,6 +245,10 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp
branch={branch}
component={component}
measures={measures}
+ appLeak={appLeak}
+ period={period}
+ loading={loadingStatus}
+ qualityGate={qualityGate}
/>
) : (
<MeasuresPanelNoNewCode
@@ -262,6 +268,8 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp
qgStatuses={qgStatuses}
component={component}
measures={measures}
+ loading={loadingStatus}
+ qualityGate={qualityGate}
/>
</>
)}
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
new file mode 100644
index 00000000000..82cfda5f355
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/overview/branches/BranchSummaryStyles.tsx
@@ -0,0 +1,69 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+import styled from '@emotion/styled';
+import { themeColor } from 'design-system/lib';
+
+export const GridContainer = styled.div`
+ --grids-gaps: var(--echoes-dimension-space-500);
+ display: grid;
+ grid-template-columns: repeat(12, minmax(0, 1fr));
+ gap: var(--grids-gaps);
+`;
+
+export const StyleMeasuresCard = styled.div`
+ box-sizing: border-box;
+ position: relative;
+
+ &:not(:last-child):before {
+ content: '';
+ position: absolute;
+ top: 0;
+ right: calc(var(--grids-gaps) / -2);
+ height: 100%;
+ width: 1px;
+ background: ${themeColor('pageBlockBorder')};
+ }
+
+ &:not(:last-child):after {
+ content: '';
+ position: absolute;
+ bottom: calc(var(--grids-gaps) / -2);
+ right: 0;
+ left: 0px;
+ height: 1px;
+ width: 100vw;
+ background: ${themeColor('pageBlockBorder')};
+ }
+`;
+
+export const StyledConditionsCard = styled.div`
+ box-sizing: border-box;
+ position: relative;
+ &:before {
+ content: '';
+ position: absolute;
+ top: 0;
+ right: calc(var(--grids-gaps) / -2);
+ height: 100%;
+ width: 1px;
+ background: ${themeColor('pageBlockBorder')};
+ }
+`;
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
new file mode 100644
index 00000000000..d81e6250b12
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/overview/branches/FailedConditions.tsx
@@ -0,0 +1,80 @@
+/*
+ * 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 { CardSeparator, TextError } from 'design-system';
+import _ from 'lodash';
+import * as React from 'react';
+import { FormattedMessage } from 'react-intl';
+import { isDiffMetric } from '../../../helpers/measures';
+import { QualityGateStatus } from '../../../types/quality-gates';
+import { QualityGate } from '../../../types/types';
+import ZeroNewIssuesSimplificationGuide from '../components/ZeroNewIssuesSimplificationGuide';
+import QualityGateConditions from './QualityGateConditions';
+
+export interface FailedConditionsProps {
+ isApplication?: boolean;
+ isNewCode: boolean;
+ qgStatus: QualityGateStatus;
+ qualityGate?: QualityGate;
+}
+
+export default function FailedConditions({
+ isApplication,
+ isNewCode,
+ qualityGate,
+ qgStatus,
+}: FailedConditionsProps) {
+ const { failedConditions, branchLike } = qgStatus;
+ const [newCodeFailedConditions, overallFailedConditions] = _.partition(
+ failedConditions,
+ (condition) => isDiffMetric(condition.metric),
+ );
+
+ return (
+ <>
+ {!isApplication && (
+ <>
+ <TextError
+ className="sw-mb-3"
+ text={
+ <FormattedMessage
+ id="quality_gates.conditions.x_conditions_failed"
+ values={{
+ conditions: isNewCode
+ ? newCodeFailedConditions.length
+ : overallFailedConditions.length,
+ }}
+ />
+ }
+ />
+ <CardSeparator />
+ </>
+ )}
+ {qualityGate?.isBuiltIn && isNewCode && (
+ <ZeroNewIssuesSimplificationGuide qualityGate={qualityGate} />
+ )}
+ <QualityGateConditions
+ component={qgStatus}
+ branchLike={branchLike}
+ failedConditions={isNewCode ? newCodeFailedConditions : overallFailedConditions}
+ isBuiltInQualityGate={isNewCode && qualityGate?.isBuiltIn}
+ />
+ </>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelPercentCards.tsx b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelPercentCards.tsx
deleted file mode 100644
index faeda3e4f70..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelPercentCards.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import * as React from 'react';
-import { MetricKey } from '~sonar-aligned/types/metrics';
-import { getComponentDrilldownUrl } from '../../../helpers/urls';
-import { BranchLike } from '../../../types/branch-like';
-import { isApplication } from '../../../types/component';
-import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
-import { Component, MeasureEnhanced } from '../../../types/types';
-import MeasuresCardPercent from '../components/MeasuresCardPercent';
-import { MeasurementType, getMeasurementMetricKey } from '../utils';
-
-interface Props {
- branch?: BranchLike;
- component: Component;
- conditions: QualityGateStatusConditionEnhanced[];
- measures: MeasureEnhanced[];
- useDiffMetric?: boolean;
-}
-
-/**
- * Renders Coverage and Duplication cards for the Overview page.
- */
-export default function MeasuresPanelPercentCards(props: Readonly<Props>) {
- const { useDiffMetric, branch, component, measures, conditions } = props;
-
- const isApp = isApplication(component.qualifier);
-
- return (
- <>
- <MeasuresCardPercent
- branchLike={branch}
- componentKey={component.key}
- conditions={conditions}
- measures={measures}
- measurementType={MeasurementType.Coverage}
- label="overview.quality_gate.coverage"
- url={getComponentDrilldownUrl({
- componentKey: component.key,
- metric: getMeasurementMetricKey(MeasurementType.Coverage, Boolean(useDiffMetric)),
- branchLike: branch,
- listView: true,
- })}
- conditionMetric={useDiffMetric ? MetricKey.new_coverage : MetricKey.coverage}
- linesMetric={useDiffMetric ? MetricKey.new_lines_to_cover : MetricKey.lines_to_cover}
- useDiffMetric={useDiffMetric}
- showRequired={!isApp}
- />
-
- <MeasuresCardPercent
- branchLike={branch}
- componentKey={component.key}
- conditions={conditions}
- measures={measures}
- measurementType={MeasurementType.Duplication}
- label="overview.quality_gate.duplications"
- url={getComponentDrilldownUrl({
- componentKey: component.key,
- metric: getMeasurementMetricKey(MeasurementType.Duplication, Boolean(useDiffMetric)),
- branchLike: branch,
- listView: true,
- })}
- conditionMetric={
- useDiffMetric
- ? MetricKey.new_duplicated_lines_density
- : MetricKey.duplicated_lines_density
- }
- linesMetric={useDiffMetric ? MetricKey.new_lines : MetricKey.lines}
- useDiffMetric={useDiffMetric}
- showRequired={!isApp}
- />
- </>
- );
-}
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 9d86007dc2f..6c061fd454b 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
@@ -18,8 +18,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import styled from '@emotion/styled';
+import classNames from 'classnames';
import {
- LightGreyCard,
LightLabel,
MetricsRatingBadge,
NoDataIcon,
@@ -41,32 +41,48 @@ import {
import { MetricKey, MetricType } from '~sonar-aligned/types/metrics';
import { getLeakValue } from '../../../components/measure/utils';
import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils';
-import { findMeasure, formatRating } from '../../../helpers/measures';
-import { CodeScope } from '../../../helpers/urls';
+import { translate } from '../../../helpers/l10n';
+import { findMeasure, formatRating, isDiffMetric } from '../../../helpers/measures';
+import { CodeScope, getComponentDrilldownUrl } from '../../../helpers/urls';
+import { ApplicationPeriod } from '../../../types/application';
import { Branch } from '../../../types/branch-like';
import { isApplication } from '../../../types/component';
import { IssueStatus } from '../../../types/issues';
import { QualityGateStatus } from '../../../types/quality-gates';
-import { Component, MeasureEnhanced } from '../../../types/types';
+import { CaycStatus, Component, MeasureEnhanced, Period, QualityGate } from '../../../types/types';
import { IssueMeasuresCardInner } from '../components/IssueMeasuresCardInner';
import MeasuresCardNumber from '../components/MeasuresCardNumber';
-import { Status, getConditionRequiredLabel } from '../utils';
-import MeasuresPanelPercentCards from './MeasuresPanelPercentCards';
+import MeasuresCardPercent from '../components/MeasuresCardPercent';
+import {
+ MeasurementType,
+ Status,
+ getConditionRequiredLabel,
+ getMeasurementMetricKey,
+} from '../utils';
+import { GridContainer, StyleMeasuresCard, StyledConditionsCard } from './BranchSummaryStyles';
+import { LeakPeriodInfo } from './LeakPeriodInfo';
+import QualityGatePanel from './QualityGatePanel';
interface Props {
+ appLeak?: ApplicationPeriod;
branch?: Branch;
component: Component;
+ loading?: boolean;
measures: MeasureEnhanced[];
+ period?: Period;
qgStatuses?: QualityGateStatus[];
+ qualityGate?: QualityGate;
}
export default function NewCodeMeasuresPanel(props: Readonly<Props>) {
- const { branch, component, measures, qgStatuses } = props;
+ const { appLeak, branch, component, measures, qgStatuses, period, loading, qualityGate } = props;
const intl = useIntl();
const isApp = isApplication(component.qualifier);
const conditions = qgStatuses?.flatMap((qg) => qg.conditions) ?? [];
+ const totalFailedCondition = qgStatuses?.flatMap((qg) => qg.failedConditions) ?? [];
+ const totalNewFailedCondition = totalFailedCondition.filter((c) => isDiffMetric(c.metric));
const newIssues = getLeakValue(findMeasure(measures, MetricKey.new_violations));
const newIssuesCondition = conditions.find((c) => c.metric === MetricKey.new_violations);
const issuesConditionFailed = newIssuesCondition?.level === Status.ERROR;
@@ -111,96 +127,186 @@ export default function NewCodeMeasuresPanel(props: Readonly<Props>) {
);
}
- return (
- <div className="sw-grid sw-grid-cols-2 sw-gap-4 sw-mt-6" id={getTabPanelId(CodeScope.New)}>
- <LightGreyCard className="sw-flex sw-col-span-2 sw-rounded-2 sw-gap-4">
- <IssueMeasuresCardInner
- data-testid="overview__measures-new_issues"
- disabled={component.needIssueSync}
- className="sw-w-1/2"
- metric={MetricKey.new_violations}
- value={formatMeasure(newIssues, MetricType.ShortInteger)}
- header={intl.formatMessage({
- id: 'overview.new_issues',
- })}
- url={getComponentIssuesUrl(component.key, {
- ...getBranchLikeQuery(branch),
- ...DEFAULT_ISSUES_QUERY,
- inNewCodePeriod: 'true',
- })}
- failed={issuesConditionFailed}
- icon={issuesConditionFailed && <TrendUpCircleIcon />}
- footer={issuesFooter}
- />
- <StyledCardSeparator />
- <IssueMeasuresCardInner
- data-testid="overview__measures-accepted_issues"
- disabled={Boolean(component.needIssueSync) || !newAcceptedIssues}
- className="sw-w-1/2"
- metric={MetricKey.new_accepted_issues}
- value={formatMeasure(newAcceptedIssues, MetricType.ShortInteger)}
- header={intl.formatMessage({
- id: 'overview.accepted_issues',
- })}
- url={getComponentIssuesUrl(component.key, {
- ...getBranchLikeQuery(branch),
- issueStatuses: IssueStatus.Accepted,
- inNewCodePeriod: 'true',
- })}
- footer={acceptedIssuesFooter}
- icon={
- <SnoozeCircleIcon
- color={
- newAcceptedIssues === '0' ? 'overviewCardDefaultIcon' : 'overviewCardWarningIcon'
- }
- />
- }
- />
- </LightGreyCard>
+ const leakPeriod = isApp ? appLeak : period;
- <MeasuresPanelPercentCards
- useDiffMetric
- branch={branch}
- component={component}
- measures={measures}
- conditions={conditions}
- />
+ 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;
- <MeasuresCardNumber
- label={
- newSecurityHotspots === '1'
- ? 'issue.type.SECURITY_HOTSPOT'
- : 'issue.type.SECURITY_HOTSPOT.plural'
- }
- url={getComponentSecurityHotspotsUrl(component.key, branch, {
- inNewCodePeriod: 'true',
- })}
- value={newSecurityHotspots}
- metric={MetricKey.new_security_hotspots}
- conditions={conditions}
- conditionMetric={MetricKey.new_security_hotspots_reviewed}
- showRequired={!isApp}
- icon={
- newSecurityReviewRating ? (
- <MetricsRatingBadge
- label={newSecurityReviewRating}
- rating={formatRating(newSecurityReviewRating)}
- size="md"
+ const noConditionsAndWarningForNewCode =
+ totalNewFailedCondition.length === 0 && !showCaycWarningInApp && !showCaycWarningInProject;
+
+ const isTwoColumns = !noConditionsAndWarningForNewCode;
+ const isThreeColumns = noConditionsAndWarningForNewCode;
+
+ return (
+ <div id={getTabPanelId(CodeScope.New)}>
+ {leakPeriod && (
+ <span
+ className="sw-body-xs sw-flex sw-items-center sw-mt-8 sw-mr-6"
+ data-spotlight-id="cayc-promotion-2"
+ >
+ <LightLabel className="sw-mr-1">{translate('overview.new_code')}:</LightLabel>
+ <b className="sw-flex">
+ <LeakPeriodInfo leakPeriod={leakPeriod} />
+ </b>
+ </span>
+ )}
+ <GridContainer className=" sw-relative sw-overflow-hidden sw-mt-8 js-summary">
+ {!noConditionsAndWarningForNewCode && (
+ <StyledConditionsCard className="sw-row-span-4 sw-col-span-4">
+ <QualityGatePanel
+ component={component}
+ loading={loading}
+ qgStatuses={qgStatuses}
+ qualityGate={qualityGate}
+ isNewCode
+ showCaycWarningInApp={showCaycWarningInApp}
+ showCaycWarningInProject={showCaycWarningInProject ?? false}
+ totalFailedConditionLength={totalNewFailedCondition.length}
/>
- ) : (
- <NoDataIcon size="md" />
- )
- }
- />
+ </StyledConditionsCard>
+ )}
+ <StyleMeasuresCard
+ className={classNames({
+ 'sw-col-span-4': isTwoColumns,
+ 'sw-col-span-6': isThreeColumns,
+ })}
+ >
+ <IssueMeasuresCardInner
+ data-testid="overview__measures-new_issues"
+ disabled={component.needIssueSync}
+ metric={MetricKey.new_violations}
+ value={formatMeasure(newIssues, MetricType.ShortInteger)}
+ header={intl.formatMessage({
+ id: 'overview.new_issues',
+ })}
+ url={getComponentIssuesUrl(component.key, {
+ ...getBranchLikeQuery(branch),
+ ...DEFAULT_ISSUES_QUERY,
+ inNewCodePeriod: 'true',
+ })}
+ failed={issuesConditionFailed}
+ icon={issuesConditionFailed && <TrendUpCircleIcon />}
+ footer={issuesFooter}
+ />
+ </StyleMeasuresCard>
+ <StyleMeasuresCard
+ className={classNames({
+ 'sw-col-span-4': isTwoColumns,
+ 'sw-col-span-6': isThreeColumns,
+ })}
+ >
+ <IssueMeasuresCardInner
+ data-testid="overview__measures-accepted_issues"
+ disabled={Boolean(component.needIssueSync) || !newAcceptedIssues}
+ metric={MetricKey.new_accepted_issues}
+ value={formatMeasure(newAcceptedIssues, MetricType.ShortInteger)}
+ header={intl.formatMessage({
+ id: 'overview.accepted_issues',
+ })}
+ url={getComponentIssuesUrl(component.key, {
+ ...getBranchLikeQuery(branch),
+ issueStatuses: IssueStatus.Accepted,
+ inNewCodePeriod: 'true',
+ })}
+ footer={acceptedIssuesFooter}
+ icon={
+ <SnoozeCircleIcon
+ color={
+ newAcceptedIssues === '0' ? 'overviewCardDefaultIcon' : 'overviewCardWarningIcon'
+ }
+ />
+ }
+ />
+ </StyleMeasuresCard>
+ <StyleMeasuresCard className="sw-col-span-4">
+ <MeasuresCardPercent
+ branchLike={branch}
+ componentKey={component.key}
+ conditions={conditions}
+ measures={measures}
+ measurementType={MeasurementType.Coverage}
+ label="overview.quality_gate.coverage"
+ url={getComponentDrilldownUrl({
+ componentKey: component.key,
+ metric: getMeasurementMetricKey(MeasurementType.Coverage, true),
+ branchLike: branch,
+ listView: true,
+ })}
+ conditionMetric={MetricKey.new_coverage}
+ linesMetric={MetricKey.new_lines_to_cover}
+ useDiffMetric
+ showRequired={!isApp}
+ />
+ </StyleMeasuresCard>
+ <StyleMeasuresCard className="sw-col-span-4">
+ <MeasuresCardPercent
+ branchLike={branch}
+ componentKey={component.key}
+ conditions={conditions}
+ measures={measures}
+ measurementType={MeasurementType.Duplication}
+ label="overview.quality_gate.duplications"
+ url={getComponentDrilldownUrl({
+ componentKey: component.key,
+ metric: getMeasurementMetricKey(MeasurementType.Duplication, true),
+ branchLike: branch,
+ listView: true,
+ })}
+ conditionMetric={MetricKey.new_duplicated_lines_density}
+ linesMetric={MetricKey.new_lines}
+ useDiffMetric
+ showRequired={!isApp}
+ />
+ </StyleMeasuresCard>
+ <StyleMeasuresCard className="sw-col-span-4">
+ <MeasuresCardNumber
+ label={
+ newSecurityHotspots === '1'
+ ? 'issue.type.SECURITY_HOTSPOT'
+ : 'issue.type.SECURITY_HOTSPOT.plural'
+ }
+ url={getComponentSecurityHotspotsUrl(component.key, branch, {
+ inNewCodePeriod: 'true',
+ })}
+ value={newSecurityHotspots}
+ metric={MetricKey.new_security_hotspots}
+ conditions={conditions}
+ conditionMetric={MetricKey.new_security_hotspots_reviewed}
+ showRequired={!isApp}
+ icon={
+ newSecurityReviewRating ? (
+ <MetricsRatingBadge
+ label={newSecurityReviewRating}
+ rating={formatRating(newSecurityReviewRating)}
+ size="md"
+ />
+ ) : (
+ <NoDataIcon size="md" />
+ )
+ }
+ />
+ </StyleMeasuresCard>
+ </GridContainer>
</div>
);
}
-const StyledCardSeparator = styled.div`
- width: 1px;
- background-color: ${themeColor('projectCardBorder')};
-`;
-
const StyledInfoMessage = styled.div`
background-color: ${themeColor('projectCardInfo')};
`;
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 9bd22123d35..263cf0d6028 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
@@ -17,6 +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 classNames from 'classnames';
import {
MetricsRatingBadge,
NoDataIcon,
@@ -33,40 +34,87 @@ import {
getComponentSecurityHotspotsUrl,
} from '~sonar-aligned/helpers/urls';
import { MetricKey, MetricType } from '~sonar-aligned/types/metrics';
-import { findMeasure, formatRating } from '../../../helpers/measures';
-import { CodeScope } from '../../../helpers/urls';
+import { findMeasure, formatRating, isDiffMetric } from '../../../helpers/measures';
+import { CodeScope, getComponentDrilldownUrl } from '../../../helpers/urls';
import { Branch } from '../../../types/branch-like';
import { SoftwareQuality } from '../../../types/clean-code-taxonomy';
import { isApplication } from '../../../types/component';
import { IssueStatus } from '../../../types/issues';
import { QualityGateStatus } from '../../../types/quality-gates';
-import { Component, MeasureEnhanced } from '../../../types/types';
+import { CaycStatus, Component, MeasureEnhanced, QualityGate } from '../../../types/types';
import MeasuresCard from '../components/MeasuresCard';
import MeasuresCardNumber from '../components/MeasuresCardNumber';
-import MeasuresPanelPercentCards from './MeasuresPanelPercentCards';
+import MeasuresCardPercent from '../components/MeasuresCardPercent';
+import { MeasurementType, getMeasurementMetricKey } from '../utils';
+import { GridContainer, StyleMeasuresCard, StyledConditionsCard } from './BranchSummaryStyles';
+import QualityGatePanel from './QualityGatePanel';
import SoftwareImpactMeasureCard from './SoftwareImpactMeasureCard';
export interface OverallCodeMeasuresPanelProps {
branch?: Branch;
component: Component;
+ loading?: boolean;
measures: MeasureEnhanced[];
qgStatuses?: QualityGateStatus[];
+ qualityGate?: QualityGate;
}
export default function OverallCodeMeasuresPanel(props: Readonly<OverallCodeMeasuresPanelProps>) {
- const { branch, qgStatuses, component, measures } = props;
+ const { branch, qgStatuses, component, measures, loading, qualityGate } = props;
const intl = useIntl();
const isApp = isApplication(component.qualifier);
const conditions = qgStatuses?.flatMap((qg) => qg.conditions) ?? [];
+ const totalFailedCondition = qgStatuses?.flatMap((qg) => qg.failedConditions) ?? [];
+ const totalOverallFailedCondition = totalFailedCondition.filter((c) => !isDiffMetric(c.metric));
const acceptedIssues = findMeasure(measures, MetricKey.accepted_issues)?.value;
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;
+
return (
- <div id={getTabPanelId(CodeScope.Overall)} className="sw-mt-6">
- <div className="sw-flex sw-gap-4">
+ <GridContainer
+ id={getTabPanelId(CodeScope.Overall)}
+ className={classNames('sw-grid sw-gap-12 sw-relative sw-overflow-hidden sw-mt-8 js-summary', {
+ 'sw-grid-cols-3': noConditionsAndWarningForOverallCode,
+ 'sw-grid-cols-4': !noConditionsAndWarningForOverallCode,
+ })}
+ >
+ {!noConditionsAndWarningForOverallCode && (
+ <StyledConditionsCard className="sw-row-span-4">
+ <QualityGatePanel
+ component={component}
+ loading={loading}
+ qgStatuses={qgStatuses}
+ qualityGate={qualityGate}
+ showCaycWarningInApp={showCaycWarningInApp}
+ showCaycWarningInProject={showCaycWarningInProject ?? false}
+ totalFailedConditionLength={totalOverallFailedCondition.length}
+ />
+ </StyledConditionsCard>
+ )}
+ <StyleMeasuresCard>
<SoftwareImpactMeasureCard
branch={branch}
component={component}
@@ -75,6 +123,8 @@ export default function OverallCodeMeasuresPanel(props: Readonly<OverallCodeMeas
ratingMetricKey={MetricKey.security_rating}
measures={measures}
/>
+ </StyleMeasuresCard>
+ <StyleMeasuresCard>
<SoftwareImpactMeasureCard
branch={branch}
component={component}
@@ -83,6 +133,8 @@ export default function OverallCodeMeasuresPanel(props: Readonly<OverallCodeMeas
ratingMetricKey={MetricKey.reliability_rating}
measures={measures}
/>
+ </StyleMeasuresCard>
+ <StyleMeasuresCard>
<SoftwareImpactMeasureCard
branch={branch}
component={component}
@@ -91,9 +143,8 @@ export default function OverallCodeMeasuresPanel(props: Readonly<OverallCodeMeas
ratingMetricKey={MetricKey.sqale_rating}
measures={measures}
/>
- </div>
-
- <div className="sw-grid sw-grid-cols-2 sw-gap-4 sw-mt-4">
+ </StyleMeasuresCard>
+ <StyleMeasuresCard>
<MeasuresCard
url={getComponentIssuesUrl(component.key, {
...getBranchLikeQuery(branch),
@@ -115,14 +166,46 @@ export default function OverallCodeMeasuresPanel(props: Readonly<OverallCodeMeas
})}
</TextSubdued>
</MeasuresCard>
-
- <MeasuresPanelPercentCards
- branch={branch}
- component={component}
+ </StyleMeasuresCard>
+ <StyleMeasuresCard>
+ <MeasuresCardPercent
+ branchLike={branch}
+ componentKey={component.key}
+ conditions={conditions}
measures={measures}
+ measurementType={MeasurementType.Coverage}
+ label="overview.quality_gate.coverage"
+ url={getComponentDrilldownUrl({
+ componentKey: component.key,
+ metric: getMeasurementMetricKey(MeasurementType.Coverage, false),
+ branchLike: branch,
+ listView: true,
+ })}
+ conditionMetric={MetricKey.coverage}
+ linesMetric={MetricKey.lines_to_cover}
+ showRequired={!isApp}
+ />
+ </StyleMeasuresCard>
+ <StyleMeasuresCard>
+ <MeasuresCardPercent
+ branchLike={branch}
+ componentKey={component.key}
conditions={conditions}
+ measures={measures}
+ measurementType={MeasurementType.Duplication}
+ label="overview.quality_gate.duplications"
+ url={getComponentDrilldownUrl({
+ componentKey: component.key,
+ metric: getMeasurementMetricKey(MeasurementType.Duplication, false),
+ branchLike: branch,
+ listView: true,
+ })}
+ conditionMetric={MetricKey.duplicated_lines_density}
+ linesMetric={MetricKey.lines}
+ showRequired={!isApp}
/>
-
+ </StyleMeasuresCard>
+ <StyleMeasuresCard>
<MeasuresCardNumber
label={
securityHotspots === '1'
@@ -147,7 +230,7 @@ export default function OverallCodeMeasuresPanel(props: Readonly<OverallCodeMeas
)
}
/>
- </div>
- </div>
+ </StyleMeasuresCard>
+ </GridContainer>
);
}
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 4899399f211..51d9dd85820 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
@@ -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 { BasicSeparator, Link } from 'design-system';
+import { CardSeparator, Link } from 'design-system';
import { sortBy } from 'lodash';
import * as React from 'react';
import { MetricKey } from '~sonar-aligned/types/metrics';
@@ -88,7 +88,7 @@ export function QualityGateConditions(props: Readonly<QualityGateConditionsProps
condition={condition}
/>
)}
- <BasicSeparator />
+ <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 6da82da5ca3..985cfa72f48 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,9 +17,12 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { BasicSeparator, Card, Spinner } from 'design-system';
+import { Card, CardSeparator, Spinner, TextError } from 'design-system';
import * as React from 'react';
+import { FormattedMessage } from 'react-intl';
import { ComponentQualifier } from '~sonar-aligned/types/component';
+import { translate } from '../../../helpers/l10n';
+import { isDiffMetric } from '../../../helpers/measures';
import { isApplication } from '../../../types/component';
import { QualityGateStatus } from '../../../types/quality-gates';
import { CaycStatus, Component, QualityGate } from '../../../types/types';
@@ -27,31 +30,38 @@ import IgnoredConditionWarning from '../components/IgnoredConditionWarning';
import ApplicationNonCaycProjectWarning from './ApplicationNonCaycProjectWarning';
import CleanAsYouCodeWarning from './CleanAsYouCodeWarning';
import QualityGatePanelSection from './QualityGatePanelSection';
-import QualityGateStatusPassedView from './QualityGateStatusPassedView';
+import SonarLintPromotion from './SonarLintPromotion';
export interface QualityGatePanelProps {
component: Pick<Component, 'key' | 'qualifier' | 'qualityGate'>;
+ isNewCode?: boolean;
loading?: boolean;
qgStatuses?: QualityGateStatus[];
qualityGate?: QualityGate;
+ showCaycWarningInApp: boolean;
+ showCaycWarningInProject: boolean;
+ totalFailedConditionLength: number;
}
export function QualityGatePanel(props: QualityGatePanelProps) {
- const { component, loading, qgStatuses = [], qualityGate } = props;
+ const {
+ component,
+ loading,
+ qgStatuses = [],
+ qualityGate,
+ isNewCode = false,
+ totalFailedConditionLength,
+ showCaycWarningInProject,
+ showCaycWarningInApp,
+ } = props;
if (qgStatuses === undefined) {
return null;
}
- const overallLevel = qgStatuses.map((s) => s.status).includes('ERROR') ? 'ERROR' : 'OK';
- const success = overallLevel === 'OK';
-
const failedQgStatuses = qgStatuses.filter((qgStatus) => qgStatus.failedConditions.length > 0);
- const overallFailedConditionsCount = qgStatuses.reduce(
- (acc, qgStatus) => acc + qgStatus.failedConditions.length,
- 0,
- );
+ const totalFailedCondition = qgStatuses?.flatMap((qg) => qg.failedConditions) ?? [];
const isApp = isApplication(component.qualifier);
@@ -66,43 +76,62 @@ export function QualityGatePanel(props: QualityGatePanelProps) {
qgStatuses.some((p) => Boolean(p.ignoredConditions));
return (
- <div data-testid="overview__quality-gate-panel">
- <div className="sw-pt-5">
+ <div data-testid="overview__quality-gate-panel-conditions">
+ <div>
<Spinner loading={loading}>
- {success && <QualityGateStatusPassedView />}
-
- {showIgnoredConditionWarning && <IgnoredConditionWarning />}
-
- {!success && <BasicSeparator />}
+ {showIgnoredConditionWarning && isNewCode && <IgnoredConditionWarning />}
+
+ {isApp && (
+ <>
+ <TextError
+ className="sw-mb-3"
+ text={
+ <FormattedMessage
+ defaultMessage={translate('quality_gates.conditions.x_conditions_failed')}
+ id="quality_gates.conditions.x_conditions_failed"
+ values={{
+ conditions: totalFailedConditionLength,
+ }}
+ />
+ }
+ />
+ <CardSeparator />
+ </>
+ )}
- {overallFailedConditionsCount > 0 && (
+ {totalFailedCondition.length > 0 && (
<div data-test="overview__quality-gate-conditions">
- {failedQgStatuses.map((qgStatus, qgStatusIdx) => (
- <QualityGatePanelSection
- isApplication={isApp}
- isLastStatus={qgStatusIdx === failedQgStatuses.length - 1}
- key={qgStatus.key}
- qgStatus={qgStatus}
- qualityGate={qualityGate}
- />
- ))}
+ {failedQgStatuses.map((qgStatus, qgStatusIdx) => {
+ const failedConditionLength = qgStatus.failedConditions.filter((con) =>
+ isNewCode ? isDiffMetric(con.metric) : !isDiffMetric(con.metric),
+ ).length;
+ if (failedConditionLength > 0) {
+ return (
+ <QualityGatePanelSection
+ isApplication={isApp}
+ isLastStatus={qgStatusIdx === failedQgStatuses.length - 1}
+ key={qgStatus.key}
+ qgStatus={qgStatus}
+ qualityGate={qualityGate}
+ isNewCode={isNewCode}
+ />
+ );
+ }
+ })}
</div>
)}
</Spinner>
</div>
- {nonCaycProjectsInApp.length > 0 && (
- <ApplicationNonCaycProjectWarning projects={nonCaycProjectsInApp} />
+ {showCaycWarningInApp && <ApplicationNonCaycProjectWarning projects={nonCaycProjectsInApp} />}
+
+ {showCaycWarningInProject && (
+ <Card className="sw-mt-4 sw-body-sm">
+ <CleanAsYouCodeWarning component={component} />
+ </Card>
)}
- {qgStatuses.length === 1 &&
- qgStatuses[0].caycStatus === CaycStatus.NonCompliant &&
- qualityGate?.actions?.manageConditions &&
- !isApp && (
- <Card className="sw-mt-4 sw-body-sm">
- <CleanAsYouCodeWarning component={component} />
- </Card>
- )}
+ <SonarLintPromotion qgConditions={qgStatuses?.flatMap((qg) => qg.failedConditions)} />
</div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanelSection.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanelSection.tsx
index d591c286381..df91989c4a8 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanelSection.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanelSection.tsx
@@ -17,125 +17,33 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { BasicSeparator, BorderlessAccordion, TextMuted } from 'design-system';
+import { BorderlessAccordion, CardSeparator } from 'design-system';
import * as React from 'react';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { isDiffMetric } from '../../../helpers/measures';
-import { BranchLike } from '../../../types/branch-like';
-import {
- QualityGateStatus,
- QualityGateStatusConditionEnhanced,
-} from '../../../types/quality-gates';
+import { translateWithParameters } from '../../../helpers/l10n';
+import { QualityGateStatus } from '../../../types/quality-gates';
import { QualityGate } from '../../../types/types';
-import ZeroNewIssuesSimplificationGuide from '../components/ZeroNewIssuesSimplificationGuide';
-import QualityGateConditions from './QualityGateConditions';
+import FailedConditions from './FailedConditions';
export interface QualityGatePanelSectionProps {
- branchLike?: BranchLike;
isApplication?: boolean;
isLastStatus?: boolean;
+ isNewCode: boolean;
qgStatus: QualityGateStatus;
qualityGate?: QualityGate;
}
-function splitConditions(
- conditions: QualityGateStatusConditionEnhanced[],
-): [QualityGateStatusConditionEnhanced[], QualityGateStatusConditionEnhanced[]] {
- const newCodeFailedConditions = [];
- const overallFailedConditions = [];
-
- for (const condition of conditions) {
- if (isDiffMetric(condition.metric)) {
- newCodeFailedConditions.push(condition);
- } else {
- overallFailedConditions.push(condition);
- }
- }
-
- return [newCodeFailedConditions, overallFailedConditions];
-}
-
export function QualityGatePanelSection(props: QualityGatePanelSectionProps) {
- const { isApplication, isLastStatus, qgStatus, qualityGate } = props;
+ const { isApplication, isLastStatus, qgStatus, qualityGate, isNewCode } = props;
const [collapsed, setCollapsed] = React.useState(false);
const toggle = React.useCallback(() => {
setCollapsed(!collapsed);
}, [collapsed]);
- const [newCodeFailedConditions, overallFailedConditions] = splitConditions(
- qgStatus.failedConditions,
- );
-
- const showSectionTitles =
- isApplication || (overallFailedConditions.length > 0 && newCodeFailedConditions.length > 0);
-
const toggleLabel = collapsed
? translateWithParameters('overview.quality_gate.show_project_conditions_x', qgStatus.name)
: translateWithParameters('overview.quality_gate.hide_project_conditions_x', qgStatus.name);
- const newCodeText =
- newCodeFailedConditions.length === 1
- ? translate('quality_gates.conditions.new_code_1')
- : translateWithParameters(
- 'quality_gates.conditions.new_code_x',
- newCodeFailedConditions.length.toString(),
- );
-
- const overallText =
- overallFailedConditions.length === 1
- ? translate('quality_gates.conditions.overall_code_1')
- : translateWithParameters(
- 'quality_gates.conditions.overall_code_x',
- overallFailedConditions.length.toString(),
- );
-
- const renderFailedConditions = () => {
- return (
- <>
- {newCodeFailedConditions.length > 0 && (
- <>
- {showSectionTitles && (
- <>
- <p className="sw-px-2 sw-py-3">{newCodeText}</p>
-
- <BasicSeparator />
- </>
- )}
-
- {qualityGate?.isBuiltIn && (
- <ZeroNewIssuesSimplificationGuide qualityGate={qualityGate} />
- )}
- <QualityGateConditions
- component={qgStatus}
- branchLike={qgStatus.branchLike}
- failedConditions={newCodeFailedConditions}
- isBuiltInQualityGate={qualityGate?.isBuiltIn}
- />
- </>
- )}
-
- {overallFailedConditions.length > 0 && (
- <>
- {showSectionTitles && (
- <>
- <p className="sw-px-2 sw-py-3">{overallText}</p>
-
- <BasicSeparator />
- </>
- )}
-
- <QualityGateConditions
- component={qgStatus}
- branchLike={qgStatus.branchLike}
- failedConditions={overallFailedConditions}
- />
- </>
- )}
- </>
- );
- };
-
return (
<>
{isApplication ? (
@@ -147,26 +55,28 @@ export function QualityGatePanelSection(props: QualityGatePanelSectionProps) {
header={
<div className="sw-flex sw-flex-col sw-text-sm">
<span className="sw-body-sm-highlight">{qgStatus.name}</span>
-
- {collapsed && newCodeFailedConditions.length > 0 && (
- <TextMuted text={newCodeText} />
- )}
-
- {collapsed && overallFailedConditions.length > 0 && (
- <TextMuted text={overallText} />
- )}
</div>
}
>
- <BasicSeparator />
+ <CardSeparator />
- {renderFailedConditions()}
+ <FailedConditions
+ isNewCode={isNewCode}
+ isApplication={isApplication}
+ qualityGate={qualityGate}
+ qgStatus={qgStatus}
+ />
</BorderlessAccordion>
- {(!isLastStatus || collapsed) && <BasicSeparator />}
+ {(!isLastStatus || collapsed) && <CardSeparator />}
</>
) : (
- renderFailedConditions()
+ <FailedConditions
+ isNewCode={isNewCode}
+ isApplication={isApplication}
+ qualityGate={qualityGate}
+ qgStatus={qgStatus}
+ />
)}
</>
);
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusPassedView.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusPassedView.tsx
deleted file mode 100644
index 2a60ea651cb..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatusPassedView.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { OverviewQGPassedIcon } from 'design-system';
-import React from 'react';
-import { translate } from '../../../helpers/l10n';
-
-export default function QualityGateStatusPassedView() {
- return (
- <div className="sw-flex sw-items-center sw-justify-center sw-flex-col">
- <OverviewQGPassedIcon className="sw-my-12" />
- <p className="sw-mb-8">{translate('overview.passed.clean_code')}</p>
- </div>
- );
-}
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx
index 10e6ca40957..420f7e65e9e 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx
@@ -19,7 +19,7 @@
*/
import styled from '@emotion/styled';
import { LinkHighlight, LinkStandalone, Tooltip } from '@sonarsource/echoes-react';
-import { Badge, LightGreyCard, LightGreyCardTitle, TextBold, TextSubdued } from 'design-system';
+import { Badge, TextBold, TextSubdued } from 'design-system';
import * as React from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import { formatMeasure } from '~sonar-aligned/helpers/measures';
@@ -92,18 +92,18 @@ export function SoftwareImpactMeasureCard(props: Readonly<SoftwareImpactBreakdow
const failed = conditions.some((c) => c.level === Status.ERROR && c.metric === ratingMetricKey);
return (
- <LightGreyCard
+ <div
data-testid={`overview__software-impact-card-${softwareQuality}`}
- className="sw-w-1/3 sw-overflow-hidden sw-rounded-2 sw-p-4 sw-flex-col"
+ className="sw-overflow-hidden sw-rounded-2 sw-flex-col"
>
- <LightGreyCardTitle>
+ <div className="sw-flex sw-items-center">
<TextBold name={intl.formatMessage({ id: `software_quality.${softwareQuality}` })} />
{failed && (
- <Badge className="sw-h-fit" variant="deleted">
+ <Badge className="sw-h-fit sw-ml-2" variant="deleted">
<FormattedMessage id="overview.measures.failed_badge" />
</Badge>
)}
- </LightGreyCardTitle>
+ </div>
<div className="sw-flex sw-flex-col sw-gap-3">
<div className="sw-flex sw-mt-4">
<div className="sw-flex sw-gap-1 sw-items-center">
@@ -164,7 +164,7 @@ export function SoftwareImpactMeasureCard(props: Readonly<SoftwareImpactBreakdow
</div>
)}
</div>
- </LightGreyCard>
+ </div>
);
}
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 48e63751198..8d260d5fe93 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
@@ -142,7 +142,6 @@ describe('project overview', () => {
// QG panel
expect(screen.getByText('metric.level.OK')).toBeInTheDocument();
- expect(screen.getByText('overview.passed.clean_code')).toBeInTheDocument();
expect(
screen.queryByText('overview.quality_gate.conditions.cayc.warning'),
).not.toBeInTheDocument();
@@ -253,7 +252,6 @@ describe('project overview', () => {
renderBranchOverview();
expect(await screen.findByText('metric.level.ERROR')).toBeInTheDocument();
- expect(screen.getByText(/overview.X_conditions_failed/)).toBeInTheDocument();
expect(screen.getAllByText(/overview.quality_gate.required_x/)).toHaveLength(3);
expect(
screen.getByRole('link', {
@@ -616,7 +614,7 @@ describe('application overview', () => {
name: 'overview.quality_gate.hide_project_conditions_x.fourth project',
}).get(),
).toBeInTheDocument();
- expect(byText('quality_gates.conditions.new_code_1').get()).toBeInTheDocument();
+ expect(byText(/quality_gates.conditions.x_conditions_failed/).get()).toBeInTheDocument();
expect(byText('1 metric.new_violations.name').get()).toBeInTheDocument();
});
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 d0820d64bb6..af61e9ccd75 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,11 +80,17 @@ const qgStatus = mockQualityGateStatus({
status: 'ERROR' as Status,
});
-it('should render correctly for an application with 1 new code condition and 1 overall code condition', async () => {
+it('should render correctly for an application for new code section', async () => {
renderQualityGatePanelSection();
- expect(await screen.findByText('quality_gates.conditions.new_code_x.2')).toBeInTheDocument();
- expect(await screen.findByText('quality_gates.conditions.overall_code_1')).toBeInTheDocument();
+ 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', () => {
@@ -130,7 +136,7 @@ function renderQualityGatePanelSection(
) {
return renderComponent(
<CurrentUserContextProvider currentUser={currentUser}>
- <QualityGatePanelSection isApplication qgStatus={qgStatus} {...props} />
+ <QualityGatePanelSection isApplication qgStatus={qgStatus} isNewCode {...props} />
</CurrentUserContextProvider>,
);
}
diff --git a/server/sonar-web/src/main/js/apps/overview/components/MeasuresCard.tsx b/server/sonar-web/src/main/js/apps/overview/components/MeasuresCard.tsx
index 69355b2e1fe..d4cad7eba00 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/MeasuresCard.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/MeasuresCard.tsx
@@ -19,7 +19,7 @@
*/
import styled from '@emotion/styled';
import { LinkHighlight, LinkStandalone } from '@sonarsource/echoes-react';
-import { Badge, Card, themeBorder, themeColor } from 'design-system';
+import { Badge, themeColor } from 'design-system';
import * as React from 'react';
import { To } from 'react-router-dom';
import { MetricKey } from '~sonar-aligned/types/metrics';
@@ -38,10 +38,10 @@ export interface MeasuresCardProps {
export default function MeasuresCard(
props: React.PropsWithChildren<MeasuresCardProps & React.HTMLAttributes<HTMLDivElement>>,
) {
- const { failed, children, metric, icon, value, url, label, ...rest } = props;
+ const { failed, children, metric, icon, value, url, label } = props;
return (
- <StyledCard className="sw-p-6 sw-rounded-2 sw-text-base" {...rest}>
+ <div>
<ColorBold className="sw-body-sm-highlight">{translate(label)}</ColorBold>
{failed && (
<Badge className="sw-mt-1/2 sw-px-1 sw-ml-2" variant="deleted">
@@ -69,14 +69,10 @@ export default function MeasuresCard(
{icon}
</div>
{children && <div className="sw-flex sw-flex-col">{children}</div>}
- </StyledCard>
+ </div>
);
}
-const StyledCard = styled(Card)`
- border: ${themeBorder('default')};
-`;
-
const ColorBold = styled.span`
color: ${themeColor('pageTitle')};
`;