}: Readonly<CurrentBranchLikeMergeInformationProps>) {
return (
<span
- className="sw-w-[400px] sw-text-ellipsis sw-whitespace-nowrap sw-overflow-hidden sw-flex-shrink sw-min-w-0"
+ className="sw-max-w-[400px] sw-text-ellipsis sw-whitespace-nowrap sw-overflow-hidden sw-flex-shrink sw-min-w-0"
title={translateWithParameters(
'branch_like_navigation.for_merge_into_x_from_y.title',
pullRequest.target,
}
`;
+export const StyleMeasuresCardRightBorder = 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')};
+ }
+`;
+
export const StyledConditionsCard = styled.div`
box-sizing: border-box;
position: relative;
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 { BranchLike } from '../../../types/branch-like';
+import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
+import { Component, QualityGate } from '../../../types/types';
import ZeroNewIssuesSimplificationGuide from '../components/ZeroNewIssuesSimplificationGuide';
import QualityGateConditions from './QualityGateConditions';
export interface FailedConditionsProps {
+ branchLike?: BranchLike;
+ component: Pick<Component, 'key'>;
+ failedConditions: QualityGateStatusConditionEnhanced[];
isApplication?: boolean;
isNewCode: boolean;
- qgStatus: QualityGateStatus;
qualityGate?: QualityGate;
}
isApplication,
isNewCode,
qualityGate,
- qgStatus,
+ failedConditions,
+ component,
+ branchLike,
}: FailedConditionsProps) {
- const { failedConditions, branchLike } = qgStatus;
const [newCodeFailedConditions, overallFailedConditions] = _.partition(
failedConditions,
(condition) => isDiffMetric(condition.metric),
<ZeroNewIssuesSimplificationGuide qualityGate={qualityGate} />
)}
<QualityGateConditions
- component={qgStatus}
+ component={component}
branchLike={branchLike}
failedConditions={isNewCode ? newCodeFailedConditions : overallFailedConditions}
isBuiltInQualityGate={isNewCode && qualityGate?.isBuiltIn}
loading?: boolean;
qgStatuses?: QualityGateStatus[];
qualityGate?: QualityGate;
- showCaycWarningInApp: boolean;
- showCaycWarningInProject: boolean;
+ showCaycWarningInApp?: boolean;
+ showCaycWarningInProject?: boolean;
totalFailedConditionLength: number;
}
qualityGate,
isNewCode = false,
totalFailedConditionLength,
- showCaycWarningInProject,
- showCaycWarningInApp,
+ showCaycWarningInProject = false,
+ showCaycWarningInApp = false,
} = props;
if (qgStatuses === undefined) {
isNewCode={isNewCode}
isApplication={isApplication}
qualityGate={qualityGate}
- qgStatus={qgStatus}
+ failedConditions={qgStatus.failedConditions}
+ branchLike={qgStatus.branchLike}
+ component={qgStatus}
/>
</BorderlessAccordion>
isNewCode={isNewCode}
isApplication={isApplication}
qualityGate={qualityGate}
- qgStatus={qgStatus}
+ failedConditions={qgStatus.failedConditions}
+ branchLike={qgStatus.branchLike}
+ component={qgStatus}
/>
)}
</>
+++ /dev/null
-/*
- * 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 {
- HelperHintIcon,
- LightGreyCard,
- LightLabel,
- SnoozeCircleIcon,
- TextError,
- TextSubdued,
- TrendDownCircleIcon,
- TrendUpCircleIcon,
- themeColor,
-} from 'design-system';
-import * as React from 'react';
-import { useIntl } from 'react-intl';
-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 Tooltip from '../../../components/controls/Tooltip';
-import { getLeakValue } from '../../../components/measure/utils';
-import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils';
-import { findMeasure } from '../../../helpers/measures';
-import { PullRequest } from '../../../types/branch-like';
-import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
-import { Component, MeasureEnhanced } from '../../../types/types';
-import { IssueMeasuresCardInner } from '../components/IssueMeasuresCardInner';
-import { Status, getConditionRequiredLabel } from '../utils';
-
-interface Props {
- component: Component;
- conditions: QualityGateStatusConditionEnhanced[];
- measures: MeasureEnhanced[];
- pullRequest: PullRequest;
-}
-
-export default function IssueMeasuresCard(
- props: React.PropsWithChildren<Props & React.HTMLAttributes<HTMLDivElement>>,
-) {
- const { measures, conditions, component, pullRequest, ...rest } = props;
-
- const intl = useIntl();
-
- const issuesCount = getLeakValue(findMeasure(measures, MetricKey.new_violations));
- const issuesCondition = conditions.find((c) => c.metric === MetricKey.new_violations);
- const issuesConditionFailed = issuesCondition?.level === Status.ERROR;
- const fixedCount = findMeasure(measures, MetricKey.pull_request_fixed_issues)?.value;
- const acceptedCount = getLeakValue(findMeasure(measures, MetricKey.new_accepted_issues));
-
- const issuesUrl = getComponentIssuesUrl(component.key, {
- ...getBranchLikeQuery(pullRequest),
- ...DEFAULT_ISSUES_QUERY,
- });
- const fixedUrl = getComponentIssuesUrl(component.key, {
- fixedInPullRequest: pullRequest.key,
- });
- const acceptedUrl = getComponentIssuesUrl(component.key, {
- ...getBranchLikeQuery(pullRequest),
- ...DEFAULT_ISSUES_QUERY,
- issueStatuses: 'ACCEPTED',
- });
-
- return (
- <LightGreyCard className="sw-p-8 sw-rounded-2 sw-flex sw-text-base sw-gap-4" {...rest}>
- <IssueMeasuresCardInner
- className="sw-w-1/3"
- header={intl.formatMessage({ id: 'overview.new_issues' })}
- data-testid={`overview__measures-${MetricKey.new_violations}`}
- data-guiding-id={issuesConditionFailed ? 'overviewZeroNewIssuesSimplification' : undefined}
- metric={MetricKey.new_violations}
- value={formatMeasure(issuesCount, MetricType.ShortInteger)}
- url={issuesUrl}
- failed={issuesConditionFailed}
- icon={issuesConditionFailed && <TrendUpCircleIcon />}
- footer={
- issuesCondition &&
- (issuesConditionFailed ? (
- <TextError
- className="sw-font-regular sw-body-xs sw-inline"
- text={getConditionRequiredLabel(issuesCondition, intl, true)}
- />
- ) : (
- <LightLabel className="sw-body-xs">
- {getConditionRequiredLabel(issuesCondition, intl)}
- </LightLabel>
- ))
- }
- />
- <StyledCardSeparator />
- <IssueMeasuresCardInner
- className="sw-w-1/3"
- header={intl.formatMessage({ id: 'overview.accepted_issues' })}
- data-testid={`overview__measures-${MetricKey.new_accepted_issues}`}
- metric={MetricKey.new_accepted_issues}
- value={formatMeasure(acceptedCount, MetricType.ShortInteger)}
- disabled={component.needIssueSync}
- url={acceptedUrl}
- icon={
- acceptedCount && (
- <SnoozeCircleIcon
- color={acceptedCount === '0' ? 'overviewCardDefaultIcon' : 'overviewCardWarningIcon'}
- />
- )
- }
- footer={
- <TextSubdued className="sw-body-xs">
- {intl.formatMessage({ id: 'overview.accepted_issues.help' })}
- </TextSubdued>
- }
- />
- <StyledCardSeparator />
- <IssueMeasuresCardInner
- className="sw-w-1/3"
- header={
- <>
- {intl.formatMessage({ id: 'overview.pull_request.fixed_issues' })}
- <Tooltip
- content={
- <div className="sw-flex sw-flex-col sw-gap-4">
- <span>
- {intl.formatMessage({ id: 'overview.pull_request.fixed_issues.disclaimer' })}
- </span>
- <span>
- {intl.formatMessage({
- id: 'overview.pull_request.fixed_issues.disclaimer.2',
- })}
- </span>
- </div>
- }
- side="top"
- >
- <HelperHintIcon raised />
- </Tooltip>
- </>
- }
- data-testid={`overview__measures-${MetricKey.pull_request_fixed_issues}`}
- metric={MetricKey.pull_request_fixed_issues}
- value={formatMeasure(fixedCount, MetricType.ShortInteger)}
- disabled={component.needIssueSync}
- url={fixedUrl}
- icon={fixedCount && fixedCount !== '0' && <TrendDownCircleIcon />}
- footer={
- <TextSubdued className="sw-body-xs">
- {intl.formatMessage({ id: 'overview.pull_request.fixed_issues.help' })}
- </TextSubdued>
- }
- />
- </LightGreyCard>
- );
-}
-
-const StyledCardSeparator = styled.div`
- width: 1px;
- background-color: ${themeColor('projectCardBorder')};
-`;
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import classNames from 'classnames';
+import {
+ Card,
+ HelperHintIcon,
+ LightLabel,
+ SnoozeCircleIcon,
+ TextError,
+ TextSubdued,
+ TrendDownCircleIcon,
+ TrendUpCircleIcon,
+} from 'design-system/lib';
import * as React from 'react';
-import { getComponentSecurityHotspotsUrl } from '~sonar-aligned/helpers/urls';
-import { MetricKey } from '~sonar-aligned/types/metrics';
+import { useIntl } from 'react-intl';
+import {
+ getComponentIssuesUrl,
+ getComponentSecurityHotspotsUrl,
+} from '~sonar-aligned/helpers/urls';
+import { MetricKey, MetricType } from '~sonar-aligned/types/metrics';
+import Tooltip from '../../../components/controls/Tooltip';
import { getLeakValue } from '../../../components/measure/utils';
+import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils';
import { findMeasure } from '../../../helpers/measures';
import { getComponentDrilldownUrl } from '../../../helpers/urls';
+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 {
+ GridContainer,
+ StyleMeasuresCard,
+ StyleMeasuresCardRightBorder,
+ StyledConditionsCard,
+} from '../branches/BranchSummaryStyles';
+import FailedConditions from '../branches/FailedConditions';
+import { IssueMeasuresCardInner } from '../components/IssueMeasuresCardInner';
import MeasuresCardNumber from '../components/MeasuresCardNumber';
import MeasuresCardPercent from '../components/MeasuresCardPercent';
-import { MeasurementType, getMeasurementMetricKey } from '../utils';
-import IssueMeasuresCard from './IssueMeasuresCard';
+import {
+ MeasurementType,
+ Status,
+ getConditionRequiredLabel,
+ getMeasurementMetricKey,
+} from '../utils';
interface Props {
- className?: string;
component: Component;
conditions: QualityGateStatusConditionEnhanced[];
measures: MeasureEnhanced[];
}
export default function MeasuresCardPanel(props: React.PropsWithChildren<Props>) {
- const { pullRequest, component, measures, conditions, className } = props;
+ const { pullRequest, component, measures, conditions } = props;
const newSecurityHotspots = getLeakValue(
findMeasure(measures, MetricKey.new_security_hotspots),
) as string;
- return (
- <>
- <IssueMeasuresCard
- conditions={conditions}
- measures={measures}
- component={component}
- pullRequest={pullRequest}
- />
+ const intl = useIntl();
+
+ const issuesCount = getLeakValue(findMeasure(measures, MetricKey.new_violations));
+ const issuesCondition = conditions.find((c) => c.metric === MetricKey.new_violations);
+ const isIssuesConditionFailed = issuesCondition?.level === Status.ERROR;
+ const fixedCount = findMeasure(measures, MetricKey.pull_request_fixed_issues)?.value;
+ const acceptedCount = getLeakValue(findMeasure(measures, MetricKey.new_accepted_issues));
+
+ const issuesUrl = getComponentIssuesUrl(component.key, {
+ ...getBranchLikeQuery(pullRequest),
+ ...DEFAULT_ISSUES_QUERY,
+ });
+ const fixedUrl = getComponentIssuesUrl(component.key, {
+ fixedInPullRequest: pullRequest.key,
+ });
+ const acceptedUrl = getComponentIssuesUrl(component.key, {
+ ...getBranchLikeQuery(pullRequest),
+ ...DEFAULT_ISSUES_QUERY,
+ issueStatuses: 'ACCEPTED',
+ });
- <div className={classNames('sw-w-full sw-flex sw-flex-row sw-gap-4 sw-mt-4', className)}>
- <div className="sw-flex-1 sw-flex sw-flex-col sw-gap-4">
+ const totalFailedConditions = conditions.filter((condition) => condition.level === Status.ERROR);
+
+ return (
+ <Card>
+ <GridContainer
+ className={classNames('sw-relative sw-overflow-hidden sw-mt-8 js-summary', {
+ 'sw-grid-cols-3': totalFailedConditions.length === 0,
+ 'sw-grid-cols-4': totalFailedConditions.length > 0,
+ })}
+ >
+ {totalFailedConditions.length > 0 && (
+ <StyledConditionsCard className="sw-row-span-3">
+ <FailedConditions
+ branchLike={pullRequest}
+ failedConditions={totalFailedConditions}
+ isNewCode
+ component={component}
+ />
+ </StyledConditionsCard>
+ )}
+ <StyleMeasuresCard>
+ <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}
+ failed={isIssuesConditionFailed}
+ icon={isIssuesConditionFailed && <TrendUpCircleIcon />}
+ footer={
+ issuesCondition &&
+ (isIssuesConditionFailed ? (
+ <TextError
+ className="sw-font-regular sw-body-xs sw-inline"
+ text={getConditionRequiredLabel(issuesCondition, intl, true)}
+ />
+ ) : (
+ <LightLabel className="sw-body-xs">
+ {getConditionRequiredLabel(issuesCondition, intl)}
+ </LightLabel>
+ ))
+ }
+ />
+ </StyleMeasuresCard>
+ <StyleMeasuresCard>
+ <IssueMeasuresCardInner
+ header={intl.formatMessage({ id: 'overview.accepted_issues' })}
+ data-testid={`overview__measures-${MetricKey.new_accepted_issues}`}
+ metric={MetricKey.new_accepted_issues}
+ value={formatMeasure(acceptedCount, MetricType.ShortInteger)}
+ disabled={component.needIssueSync}
+ url={acceptedUrl}
+ icon={
+ acceptedCount && (
+ <SnoozeCircleIcon
+ color={
+ acceptedCount === '0' ? 'overviewCardDefaultIcon' : 'overviewCardWarningIcon'
+ }
+ />
+ )
+ }
+ footer={
+ <TextSubdued className="sw-body-xs">
+ {intl.formatMessage({ id: 'overview.accepted_issues.help' })}
+ </TextSubdued>
+ }
+ />
+ </StyleMeasuresCard>
+ <StyleMeasuresCard>
+ <IssueMeasuresCardInner
+ header={
+ <>
+ {intl.formatMessage({ id: 'overview.pull_request.fixed_issues' })}
+ <Tooltip
+ content={
+ <div className="sw-flex sw-flex-col sw-gap-4">
+ <span>
+ {intl.formatMessage({
+ id: 'overview.pull_request.fixed_issues.disclaimer',
+ })}
+ </span>
+ <span>
+ {intl.formatMessage({
+ id: 'overview.pull_request.fixed_issues.disclaimer.2',
+ })}
+ </span>
+ </div>
+ }
+ side="top"
+ >
+ <HelperHintIcon raised />
+ </Tooltip>
+ </>
+ }
+ data-testid={`overview__measures-${MetricKey.pull_request_fixed_issues}`}
+ metric={MetricKey.pull_request_fixed_issues}
+ value={formatMeasure(fixedCount, MetricType.ShortInteger)}
+ disabled={component.needIssueSync}
+ url={fixedUrl}
+ icon={fixedCount && fixedCount !== '0' && <TrendDownCircleIcon />}
+ footer={
+ <TextSubdued className="sw-body-xs">
+ {intl.formatMessage({ id: 'overview.pull_request.fixed_issues.help' })}
+ </TextSubdued>
+ }
+ />
+ </StyleMeasuresCard>
+ <StyleMeasuresCardRightBorder>
<MeasuresCardPercent
componentKey={component.key}
branchLike={pullRequest}
showRequired
useDiffMetric
/>
-
- <MeasuresCardNumber
- label={
- newSecurityHotspots === '1'
- ? 'issue.type.SECURITY_HOTSPOT'
- : 'issue.type.SECURITY_HOTSPOT.plural'
- }
- url={getComponentSecurityHotspotsUrl(component.key, pullRequest)}
- value={newSecurityHotspots}
- metric={MetricKey.new_security_hotspots}
- conditions={conditions}
- conditionMetric={MetricKey.new_security_hotspots_reviewed}
- showRequired
- />
- </div>
-
- <div className="sw-flex-1 sw-flex sw-flex-col sw-gap-4">
+ </StyleMeasuresCardRightBorder>
+ <StyleMeasuresCardRightBorder>
<MeasuresCardPercent
componentKey={component.key}
branchLike={pullRequest}
useDiffMetric
showRequired
/>
+ </StyleMeasuresCardRightBorder>
+ <div>
+ <MeasuresCardNumber
+ label={
+ newSecurityHotspots === '1'
+ ? 'issue.type.SECURITY_HOTSPOT'
+ : 'issue.type.SECURITY_HOTSPOT.plural'
+ }
+ url={getComponentSecurityHotspotsUrl(component.key, pullRequest)}
+ value={newSecurityHotspots}
+ metric={MetricKey.new_security_hotspots}
+ conditions={conditions}
+ conditionMetric={MetricKey.new_security_hotspots_reviewed}
+ showRequired
+ />
</div>
- </div>
- </>
+ </GridContainer>
+ </Card>
);
}
<PullRequestMetaTopBar pullRequest={pullRequest} measures={measures} />
<BasicSeparator className="sw-my-4" />
- <AnalysisStatus className="sw-mb-4" component={component} />
-
{ignoredConditions && <IgnoredConditionWarning />}
<div className="sw-flex sw-justify-between sw-items-start sw-my-6">
<LastAnalysisLabel analysisDate={pullRequest.analysisDate} />
</div>
+ <AnalysisStatus className="sw-mb-4" component={component} />
+
<MeasuresCardPanel
- className="sw-flex-1"
pullRequest={pullRequest}
component={component}
conditions={enhancedConditions}