/* * 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 { ButtonSecondary, FlagMessage, HeadingDark, HelperHintIcon, HighlightedSection, LightLabel, LightPrimary, Link, Note, Spinner, SubHeading, } from 'design-system'; import { differenceWith, map, uniqBy } from 'lodash'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import DocHelpTooltip from '~sonar-aligned/components/controls/DocHelpTooltip'; import { MetricKey } from '~sonar-aligned/types/metrics'; import { useAvailableFeatures } from '../../../app/components/available-features/withAvailableFeatures'; import { useMetrics } from '../../../app/components/metrics/withMetricsContext'; import DocumentationLink from '../../../components/common/DocumentationLink'; import ModalButton, { ModalProps } from '../../../components/controls/ModalButton'; import { DocLink } from '../../../helpers/doc-links'; import { useDocUrl } from '../../../helpers/docs'; import { getLocalizedMetricName, translate } from '../../../helpers/l10n'; import { Feature } from '../../../types/features'; import { CaycStatus, Condition as ConditionType, QualityGate } from '../../../types/types'; import { groupAndSortByPriorityConditions, isQualityGateOptimized } from '../utils'; import AddConditionModal from './AddConditionModal'; import CaYCConditionsSimplificationGuide from './CaYCConditionsSimplificationGuide'; import CaycCompliantBanner from './CaycCompliantBanner'; import CaycCondition from './CaycCondition'; import CaycFixOptimizeBanner from './CaycFixOptimizeBanner'; import CaycReviewUpdateConditionsModal from './ConditionReviewAndUpdateModal'; import ConditionsTable from './ConditionsTable'; import QGRecommendedIcon from './QGRecommendedIcon'; interface Props { isFetching?: boolean; qualityGate: QualityGate; } const FORBIDDEN_METRIC_TYPES = ['DATA', 'DISTRIB', 'STRING', 'BOOL']; const FORBIDDEN_METRICS: string[] = [ MetricKey.alert_status, MetricKey.releasability_rating, MetricKey.security_hotspots, MetricKey.new_security_hotspots, ]; export default function Conditions({ qualityGate, isFetching }: Readonly) { const [editing, setEditing] = React.useState( qualityGate.caycStatus === CaycStatus.NonCompliant, ); const { name } = qualityGate; const metrics = useMetrics(); const canEdit = Boolean(qualityGate.actions?.manageConditions); const { conditions = [] } = qualityGate; const existingConditions = conditions.filter((condition) => metrics[condition.metric]); const { overallCodeConditions, newCodeConditions, caycConditions } = groupAndSortByPriorityConditions(existingConditions, metrics, qualityGate.isBuiltIn); const duplicates: ConditionType[] = []; const savedConditions = existingConditions.filter((condition) => condition.id != null); savedConditions.forEach((condition) => { const sameCount = savedConditions.filter((sample) => sample.metric === condition.metric).length; if (sameCount > 1) { duplicates.push(condition); } }); const { hasFeature } = useAvailableFeatures(); const uniqDuplicates = uniqBy(duplicates, (d) => d.metric).map((condition) => ({ ...condition, metric: metrics[condition.metric], })); // set edit only when the name is change // i.e when user changes the quality gate React.useEffect(() => { setEditing(qualityGate.caycStatus === CaycStatus.NonCompliant); }, [name]); // eslint-disable-line react-hooks/exhaustive-deps const renderConditionModal = React.useCallback( ({ onClose }: ModalProps) => { const { conditions = [] } = qualityGate; const availableMetrics = differenceWith( map(metrics, (metric) => metric).filter( (metric) => !metric.hidden && !FORBIDDEN_METRIC_TYPES.includes(metric.type) && !FORBIDDEN_METRICS.includes(metric.key) && !( metric.key === MetricKey.prioritized_rule_issues && !hasFeature(Feature.PrioritizedRules) ), ), conditions, (metric, condition) => metric.key === condition.metric, ); return ( ); }, [metrics, qualityGate], ); const docUrl = useDocUrl(DocLink.CaYC); const isCompliantCustomQualityGate = qualityGate.caycStatus !== CaycStatus.NonCompliant && !qualityGate.isBuiltIn; const isOptimizing = isCompliantCustomQualityGate && !isQualityGateOptimized(qualityGate); const renderCaycModal = React.useCallback( ({ onClose }: ModalProps) => { const { conditions = [] } = qualityGate; const canEdit = Boolean(qualityGate.actions?.manageConditions); return ( setEditing(false)} conditions={conditions} scope="new-cayc" onClose={onClose} isOptimizing={isOptimizing} /> ); }, [qualityGate, metrics, isOptimizing], ); return (
{qualityGate.isBuiltIn && (
{translate('clean_as_you_code')} ), }} />
)} {isCompliantCustomQualityGate && !isOptimizing && } {isCompliantCustomQualityGate && isOptimizing && canEdit && ( )} {qualityGate.caycStatus === CaycStatus.NonCompliant && canEdit && ( )}
{translate('quality_gates.conditions')} {!qualityGate.isBuiltIn && ( )}
{(qualityGate.caycStatus === CaycStatus.NonCompliant || editing) && canEdit && ( {({ onClick }) => ( {translate('quality_gates.add_condition')} )} )}
{uniqDuplicates.length > 0 && (

{translate('quality_gates.duplicated_conditions')}

    {uniqDuplicates.map((d) => (
  • {getLocalizedMetricName(d.metric)}
  • ))}
)}
{caycConditions.length > 0 && (
{translate('quality_gates.conditions.cayc')}
    {caycConditions.map((condition) => ( ))}
{hasFeature(Feature.BranchSupport) && ( {translate('quality_gates.conditions.cayc', 'description')} )}
)} {newCodeConditions.length > 0 && (
{translate('quality_gates.conditions.new_code', 'long')} {hasFeature(Feature.BranchSupport) && ( {translate('quality_gates.conditions.new_code', 'description')} )}
)} {overallCodeConditions.length > 0 && (
{translate('quality_gates.conditions.overall_code', 'long')} {hasFeature(Feature.BranchSupport) && ( {translate('quality_gates.conditions.overall_code', 'description')} )}
)}
{qualityGate.caycStatus !== CaycStatus.NonCompliant && !editing && canEdit && (
{translate('quality_gates.cayc')}, }} /> setEditing(true)}> {translate('quality_gates.cayc.unlock_edit')}
)} {existingConditions.length === 0 && (
{translate('quality_gates.no_conditions')}
)}
); }