Browse Source

SONAR-20993 Add option to optimize quality gates CaYC compliant without 0 issues conditions (#9929)

tags/10.3.0.82913
Andrey Luiz 6 months ago
parent
commit
917bbd0d1a

+ 18
- 0
server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts View File

@@ -125,6 +125,13 @@ export class QualityGatesServiceMock {
mockQualityGate({
name: 'SonarSource way - CFamily',
conditions: [
{
id: 'AXJMbIUHPAOIsUIE3eOi',
metric: 'new_security_hotspots_reviewed',
op: 'LT',
error: '85',
isCaycCondition: true,
},
{
id: 'AXJMbIUHPAOIsUIE3eOu',
metric: 'new_coverage',
@@ -187,6 +194,17 @@ export class QualityGatesServiceMock {
isBuiltIn: false,
caycStatus: CaycStatus.NonCompliant,
}),
mockQualityGate({
name: 'Non Cayc Compliant QG',
conditions: [
{ id: 'AXJMbIUHPAOIsUIE3eNs', metric: 'new_security_rating', op: 'GT', error: '1' },
{ id: 'AXJMbIUHPAOIsUIE3eOD', metric: 'new_reliability_rating', op: 'GT', error: '1' },
{ id: 'AXJMbIUHPAOIsUIE3eOF', metric: 'new_coverage', op: 'LT', error: '80' },
],
isDefault: false,
isBuiltIn: false,
caycStatus: CaycStatus.Compliant,
}),
mockQualityGate({
name: 'Over Compliant CAYC QG',
conditions: [

+ 62
- 0
server/sonar-web/src/main/js/apps/quality-gates/components/CaycCompliantBanner.tsx View File

@@ -0,0 +1,62 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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 {
CardWithPrimaryBackground,
CheckIcon,
LightLabel,
SubHeadingHighlight,
} from 'design-system';
import React from 'react';
import { FormattedMessage } from 'react-intl';
import DocumentationLink from '../../../components/common/DocumentationLink';
import { translate } from '../../../helpers/l10n';
import { OPTIMIZED_CAYC_CONDITIONS } from '../utils';

export default function CaycCompliantBanner() {
return (
<CardWithPrimaryBackground className="sw-mb-9 sw-p-8">
<SubHeadingHighlight className="sw-mb-2">
{translate('quality_gates.cayc.banner.title')}
</SubHeadingHighlight>

<div>
<FormattedMessage
id="quality_gates.cayc.banner.description1"
values={{
cayc_link: (
<DocumentationLink to="/user-guide/clean-as-you-code/">
{translate('quality_gates.cayc')}
</DocumentationLink>
),
}}
/>
</div>
<div className="sw-my-2">{translate('quality_gates.cayc.banner.description2')}</div>
<ul className="sw-body-sm sw-flex sw-flex-col sw-gap-2">
{Object.values(OPTIMIZED_CAYC_CONDITIONS).map((condition) => (
<li key={condition.metric}>
<CheckIcon className="sw-mr-1 sw-pt-1/2" />
<LightLabel>{translate(`metric.${condition.metric}.description.positive`)}</LightLabel>
</li>
))}
</ul>
</CardWithPrimaryBackground>
);
}

+ 0
- 32
server/sonar-web/src/main/js/apps/quality-gates/components/CaycConditionsListItem.tsx View File

@@ -1,32 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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 { CheckIcon, LightLabel } from 'design-system';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';

export default function CaycConditionsListItem({ metricKey }: Readonly<{ metricKey: string }>) {
return (
<li>
<CheckIcon className="sw-mr-1 sw-pt-1/2" />
<LightLabel>{translate(`metric.${metricKey}.description.positive`)}</LightLabel>
</li>
);
}

+ 71
- 0
server/sonar-web/src/main/js/apps/quality-gates/components/CaycFixOptimizeBanner.tsx View File

@@ -0,0 +1,71 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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 { ButtonPrimary, CardWithPrimaryBackground, SubHeadingHighlight } from 'design-system';
import React from 'react';
import { FormattedMessage } from 'react-intl';
import DocumentationLink from '../../../components/common/DocumentationLink';
import ModalButton from '../../../components/controls/ModalButton';
import { translate } from '../../../helpers/l10n';

interface Props {
renderCaycModal: ({ onClose }: { onClose: () => void }) => React.ReactNode;
isOptimizing?: boolean;
}

export default function CaycNonCompliantBanner({ renderCaycModal, isOptimizing }: Readonly<Props>) {
return (
<CardWithPrimaryBackground className="sw-mb-9 sw-p-8">
<SubHeadingHighlight className="sw-mb-2">
{translate(
isOptimizing
? 'quality_gates.cayc_optimize.banner.title'
: 'quality_gates.cayc_missing.banner.title',
)}
</SubHeadingHighlight>
<div>
<FormattedMessage
id={
isOptimizing
? 'quality_gates.cayc_optimize.banner.description'
: 'quality_gates.cayc_missing.banner.description'
}
values={{
cayc_link: (
<DocumentationLink to="/user-guide/clean-as-you-code/">
{translate('quality_gates.cayc')}
</DocumentationLink>
),
}}
/>
</div>
<ModalButton modal={renderCaycModal}>
{({ onClick }) => (
<ButtonPrimary className="sw-mt-4" onClick={onClick}>
{translate(
isOptimizing
? 'quality_gates.cayc_condition.review_optimize'
: 'quality_gates.cayc_condition.review_update',
)}
</ButtonPrimary>
)}
</ModalButton>
</CardWithPrimaryBackground>
);
}

+ 15
- 4
server/sonar-web/src/main/js/apps/quality-gates/components/ConditionReviewAndUpdateModal.tsx View File

@@ -40,6 +40,7 @@ interface Props {
onSaveCondition: (newCondition: Condition, oldCondition: Condition) => void;
lockEditing: () => void;
qualityGate: QualityGate;
isOptimizing?: boolean;
}

export default function CaycReviewUpdateConditionsModal(props: Readonly<Props>) {
@@ -51,6 +52,7 @@ export default function CaycReviewUpdateConditionsModal(props: Readonly<Props>)
onAddCondition,
lockEditing,
onClose,
isOptimizing,
} = props;

const { weakConditions, missingConditions } = getWeakMissingAndNonCaycConditions(conditions);
@@ -108,8 +110,11 @@ export default function CaycReviewUpdateConditionsModal(props: Readonly<Props>)
<div className="sw-mb-10">
<SubHeading as="p" className="sw-body-sm">
<FormattedMessage
id="quality_gates.cayc.review_update_modal.description1"
defaultMessage={translate('quality_gates.cayc.review_update_modal.description1')}
id={
isOptimizing
? 'quality_gates.cayc.review_optimize_modal.description1'
: 'quality_gates.cayc.review_update_modal.description1'
}
values={{
cayc_link: (
<Link to={getDocUrl('/user-guide/clean-as-you-code/')}>
@@ -164,7 +169,9 @@ export default function CaycReviewUpdateConditionsModal(props: Readonly<Props>)
<Modal
isLarge
headerTitle={translateWithParameters(
'quality_gates.cayc.review_update_modal.header',
isOptimizing
? 'quality_gates.cayc.review_optimize_modal.header'
: 'quality_gates.cayc.review_update_modal.header',
qualityGate.name,
)}
onClose={onClose}
@@ -176,7 +183,11 @@ export default function CaycReviewUpdateConditionsModal(props: Readonly<Props>)
type="submit"
onClick={updateCaycQualityGate}
>
{translate('quality_gates.cayc.review_update_modal.confirm_text')}
{translate(
isOptimizing
? 'quality_gates.cayc.review_optimize_modal.confirm_text'
: 'quality_gates.cayc.review_update_modal.confirm_text',
)}
</ButtonPrimary>
}
secondaryButtonLabel={translate('close')}

+ 6
- 44
server/sonar-web/src/main/js/apps/quality-gates/components/ConditionValueDescription.tsx View File

@@ -18,17 +18,9 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { translate } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
import {
GRID_INDEX_OFFSET,
PERCENT_MULTIPLIER,
getMaintainabilityGrid,
} from '../../../helpers/ratings';
import { AppState } from '../../../types/appstate';
import { MetricKey, MetricType } from '../../../types/metrics';
import { GlobalSettingKeys } from '../../../types/settings';
import { MetricKey } from '../../../types/metrics';
import { Condition, Metric } from '../../../types/types';
import { GreenColorText } from './ConditionValue';

@@ -37,50 +29,22 @@ const NO_DESCRIPTION_CONDITION = [
MetricKey.new_security_hotspots_reviewed,
MetricKey.new_coverage,
MetricKey.new_duplicated_lines_density,
MetricKey.new_reliability_rating,
MetricKey.new_security_rating,
MetricKey.new_maintainability_rating,
];

interface Props {
appState: AppState;
condition: Condition;
metric: Metric;
isToBeModified?: boolean;
}

function ConditionValueDescription({
export default function ConditionValueDescription({
condition,
appState: { settings },
metric,
isToBeModified = false,
}: Readonly<Props>) {
if (condition.metric === MetricKey.new_maintainability_rating) {
const maintainabilityGrid = getMaintainabilityGrid(
settings[GlobalSettingKeys.RatingGrid] ?? '',
);
const maintainabilityRatingThreshold =
maintainabilityGrid[Math.floor(Number(condition.error)) - GRID_INDEX_OFFSET];
const ratingLetter = formatMeasure(condition.error, MetricType.Rating);

return (
<GreenColorText isToBeModified={isToBeModified}>
(
{condition.error === '1'
? translateWithParameters(
'quality_gates.cayc.new_maintainability_rating.A',
formatMeasure(maintainabilityGrid[0] * PERCENT_MULTIPLIER, MetricType.Percent),
)
: translateWithParameters(
'quality_gates.cayc.new_maintainability_rating',
ratingLetter,
formatMeasure(
maintainabilityRatingThreshold * PERCENT_MULTIPLIER,
MetricType.Percent,
),
)}
)
</GreenColorText>
);
}

return (
<GreenColorText isToBeModified={isToBeModified}>
{condition.isCaycCondition &&
@@ -99,5 +63,3 @@ function ConditionValueDescription({
</GreenColorText>
);
}

export default withAppStateContext(ConditionValueDescription);

+ 23
- 62
server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx View File

@@ -19,9 +19,7 @@
*/

import {
ButtonPrimary,
ButtonSecondary,
CardWithPrimaryBackground,
FlagMessage,
HeadingDark,
HelperHintIcon,
@@ -29,7 +27,6 @@ import {
Link,
Note,
SubHeading,
SubHeadingHighlight,
} from 'design-system';
import { differenceWith, map, uniqBy } from 'lodash';
import * as React from 'react';
@@ -51,10 +48,11 @@ import {
Metric,
QualityGate,
} from '../../../types/types';
import { CAYC_CONDITIONS, groupAndSortByPriorityConditions } from '../utils';
import { groupAndSortByPriorityConditions, isQualityGateOptimized } from '../utils';
import CaYCConditionsSimplificationGuide from './CaYCConditionsSimplificationGuide';
import CaycConditionsListItem from './CaycConditionsListItem';
import CaycCompliantBanner from './CaycCompliantBanner';
import CaycConditionsTable from './CaycConditionsTable';
import CaycFixOptimizeBanner from './CaycFixOptimizeBanner';
import ConditionModal from './ConditionModal';
import CaycReviewUpdateConditionsModal from './ConditionReviewAndUpdateModal';
import ConditionsTable from './ConditionsTable';
@@ -76,7 +74,7 @@ const FORBIDDEN_METRICS: string[] = [
MetricKey.new_security_hotspots,
];

export function Conditions({
function Conditions({
qualityGate,
metrics,
onRemoveCondition,
@@ -109,8 +107,6 @@ export function Conditions({
metric: metrics[condition.metric],
}));

const getDocUrl = useDocUrl();

// set edit only when the name is change
// i.e when user changes the quality gate
React.useEffect(() => {
@@ -143,6 +139,11 @@ export function Conditions({
[metrics, qualityGate, onAddCondition],
);

const getDocUrl = useDocUrl();
const isCompliantCustomQualityGate =
qualityGate.caycStatus !== CaycStatus.NonCompliant && !qualityGate.isBuiltIn;
const isOptimizing = isCompliantCustomQualityGate && !isQualityGateOptimized(qualityGate);

const renderCaycModal = React.useCallback(
({ onClose }: ModalProps) => {
const { conditions = [] } = qualityGate;
@@ -160,71 +161,31 @@ export function Conditions({
conditions={conditions}
scope="new-cayc"
onClose={onClose}
isOptimizing={isOptimizing}
/>
);
},
[qualityGate, metrics, updatedConditionId, onAddCondition, onRemoveCondition, onSaveCondition],
[
qualityGate,
metrics,
updatedConditionId,
onAddCondition,
onRemoveCondition,
onSaveCondition,
isOptimizing,
],
);

return (
<div>
<CaYCConditionsSimplificationGuide />

{qualityGate.caycStatus !== CaycStatus.NonCompliant && !qualityGate.isBuiltIn && (
<CardWithPrimaryBackground className="sw-mb-9 sw-p-8">
<SubHeadingHighlight className="sw-mb-2">
{translate('quality_gates.cayc.banner.title')}
</SubHeadingHighlight>

<div>
<FormattedMessage
id="quality_gates.cayc.banner.description1"
defaultMessage={translate('quality_gates.cayc.banner.description1')}
values={{
cayc_link: (
<Link to={getDocUrl('/user-guide/clean-as-you-code/')}>
{translate('quality_gates.cayc')}
</Link>
),
}}
/>
</div>
<div className="sw-my-2">{translate('quality_gates.cayc.banner.description2')}</div>
<ul className="sw-body-sm sw-flex sw-flex-col sw-gap-2">
{Object.values(CAYC_CONDITIONS).map((condition) => (
<CaycConditionsListItem key={condition.metric} metricKey={condition.metric} />
))}
</ul>
</CardWithPrimaryBackground>
{isCompliantCustomQualityGate && !isOptimizing && <CaycCompliantBanner />}
{isCompliantCustomQualityGate && isOptimizing && canEdit && (
<CaycFixOptimizeBanner renderCaycModal={renderCaycModal} isOptimizing />
)}
{qualityGate.caycStatus === CaycStatus.NonCompliant && canEdit && (
<CardWithPrimaryBackground className="sw-mb-9 sw-p-8">
<SubHeadingHighlight className="sw-mb-2">
{translate('quality_gates.cayc_missing.banner.title')}
</SubHeadingHighlight>
<div>
<FormattedMessage
id="quality_gates.cayc_missing.banner.description"
defaultMessage={translate('quality_gates.cayc_missing.banner.description')}
values={{
cayc_link: (
<Link to={getDocUrl('/user-guide/clean-as-you-code/')}>
{translate('quality_gates.cayc')}
</Link>
),
}}
/>
</div>
{canEdit && (
<ModalButton modal={renderCaycModal}>
{({ onClick }) => (
<ButtonPrimary className="sw-mt-4" onClick={onClick}>
{translate('quality_gates.cayc_condition.review_update')}
</ButtonPrimary>
)}
</ModalButton>
)}
</CardWithPrimaryBackground>
<CaycFixOptimizeBanner renderCaycModal={renderCaycModal} />
)}

<header className="sw-flex sw-items-center sw-mb-4 sw-justify-between">

+ 69
- 0
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx View File

@@ -379,6 +379,44 @@ it('should show warning banner when CAYC condition is not properly set and shoul
expect(overallConditionsWrapper.getByText('Complexity / Function')).toBeInTheDocument();
});

it('should show optimize banner when CAYC condition is not properly set and QG is compliant and should be able to update them', async () => {
const user = userEvent.setup();
qualityGateHandler.setIsAdmin(true);
renderQualityGateApp();

const qualityGate = await screen.findByText('Non Cayc Compliant QG');

await user.click(qualityGate);

expect(screen.getByText('quality_gates.cayc_optimize.banner.title')).toBeInTheDocument();
expect(screen.getByText('quality_gates.cayc_optimize.banner.description')).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'quality_gates.cayc_condition.review_optimize' }),
).toBeInTheDocument();

await user.click(
screen.getByRole('button', { name: 'quality_gates.cayc_condition.review_optimize' }),
);
expect(
screen.getByRole('dialog', {
name: 'quality_gates.cayc.review_optimize_modal.header.Non Cayc Compliant QG',
}),
).toBeInTheDocument();
expect(
screen.getByText('quality_gates.cayc.review_optimize_modal.description1'),
).toBeInTheDocument();
expect(
screen.getByText('quality_gates.cayc.review_update_modal.description2'),
).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'quality_gates.cayc.review_optimize_modal.confirm_text' }),
).toBeInTheDocument();

await user.click(
screen.getByRole('button', { name: 'quality_gates.cayc.review_optimize_modal.confirm_text' }),
);
});

it('should not warn user when quality gate is not CAYC compliant and user has no permission to edit it', async () => {
const user = userEvent.setup();
renderQualityGateApp();
@@ -391,6 +429,20 @@ it('should not warn user when quality gate is not CAYC compliant and user has no
expect(screen.queryByText('quality_gates.cayc.tooltip.message')).not.toBeInTheDocument();
});

it('should not show optimize banner when quality gate is compliant but non-CaYC and user has no permission to edit it', async () => {
const user = userEvent.setup();
renderQualityGateApp();

const nonCompliantQualityGate = await screen.findByRole('button', {
name: 'Non Cayc Compliant QG',
});

await user.click(nonCompliantQualityGate);

expect(screen.queryByRole('alert')).not.toBeInTheDocument();
expect(screen.queryByText('quality_gates.cayc.tooltip.message')).not.toBeInTheDocument();
});

it('should warn user when quality gate is not CAYC compliant and user has permission to edit it', async () => {
const user = userEvent.setup();
qualityGateHandler.setIsAdmin(true);
@@ -399,11 +451,28 @@ it('should warn user when quality gate is not CAYC compliant and user has permis
const nonCompliantQualityGate = await screen.findByRole('button', { name: /Non Cayc QG/ });

await user.click(nonCompliantQualityGate);
// expect(screen.getByTestId('conditions')).toMatchSnapshot();

expect(await screen.findByText(/quality_gates.cayc_missing.banner.title/)).toBeInTheDocument();
expect(screen.getAllByText('quality_gates.cayc.tooltip.message').length).toBeGreaterThan(0);
});

it('should show optimize banner when quality gate is compliant but non-CaYC and user has permission to edit it', async () => {
const user = userEvent.setup();
qualityGateHandler.setIsAdmin(true);
renderQualityGateApp();

const nonCompliantQualityGate = await screen.findByRole('button', {
name: /Non Cayc Compliant QG/,
});

await user.click(nonCompliantQualityGate);
// expect(screen.getByTestId('conditions')).toMatchSnapshot();

expect(await screen.findByText(/quality_gates.cayc_optimize.banner.title/)).toBeInTheDocument();
expect(screen.getAllByText('quality_gates.cayc.tooltip.message').length).toBeGreaterThan(0);
});

it('should render CaYC conditions on a separate table if Sonar way', async () => {
const user = userEvent.setup();
qualityGateHandler.setIsAdmin(true);

+ 115
- 57
server/sonar-web/src/main/js/apps/quality-gates/utils.ts View File

@@ -28,23 +28,25 @@ interface GroupedByMetricConditions {
caycConditions: Condition[];
}

type CaycMetricKeys =
| MetricKey.new_violations
type CommonCaycMetricKeys =
| MetricKey.new_security_hotspots_reviewed
| MetricKey.new_coverage
| MetricKey.new_duplicated_lines_density;

export const CAYC_CONDITIONS: Record<
CaycMetricKeys,
type OptimizedCaycMetricKeys = MetricKey.new_violations | CommonCaycMetricKeys;

type UnoptimizedCaycMetricKeys =
| MetricKey.new_reliability_rating
| MetricKey.new_security_rating
| MetricKey.new_maintainability_rating
| CommonCaycMetricKeys;

type AllCaycMetricKeys = OptimizedCaycMetricKeys | UnoptimizedCaycMetricKeys;

const COMMON_CONDITIONS: Record<
CommonCaycMetricKeys,
Condition & { shouldRenderOperator?: boolean }
> = {
[MetricKey.new_violations]: {
id: MetricKey.new_violations,
metric: MetricKey.new_violations,
op: 'GT',
error: '0',
isCaycCondition: true,
},
[MetricKey.new_security_hotspots_reviewed]: {
id: MetricKey.new_security_hotspots_reviewed,
metric: MetricKey.new_security_hotspots_reviewed,
@@ -70,6 +72,56 @@ export const CAYC_CONDITIONS: Record<
},
};

export const OPTIMIZED_CAYC_CONDITIONS: Record<
OptimizedCaycMetricKeys,
Condition & { shouldRenderOperator?: boolean }
> = {
[MetricKey.new_violations]: {
id: MetricKey.new_violations,
metric: MetricKey.new_violations,
op: 'GT',
error: '0',
isCaycCondition: true,
},
...COMMON_CONDITIONS,
};

const UNOPTIMIZED_CAYC_CONDITIONS: Record<
UnoptimizedCaycMetricKeys,
Condition & { shouldRenderOperator?: boolean }
> = {
[MetricKey.new_reliability_rating]: {
id: MetricKey.new_reliability_rating,
metric: MetricKey.new_reliability_rating,
op: 'GT',
error: '1',
isCaycCondition: true,
},
[MetricKey.new_security_rating]: {
id: MetricKey.new_security_rating,
metric: MetricKey.new_security_rating,
op: 'GT',
error: '1',
isCaycCondition: true,
},
[MetricKey.new_maintainability_rating]: {
id: MetricKey.new_maintainability_rating,
metric: MetricKey.new_maintainability_rating,
op: 'GT',
error: '1',
isCaycCondition: true,
},
...COMMON_CONDITIONS,
};

const ALL_CAYC_CONDITIONS: Record<
AllCaycMetricKeys,
Condition & { shouldRenderOperator?: boolean }
> = {
...OPTIMIZED_CAYC_CONDITIONS,
...UNOPTIMIZED_CAYC_CONDITIONS,
};

const CAYC_CONDITION_ORDER_PRIORITIES: Dict<number> = [
MetricKey.new_violations,
MetricKey.new_security_hotspots_reviewed,
@@ -79,26 +131,50 @@ const CAYC_CONDITION_ORDER_PRIORITIES: Dict<number> = [
.reverse()
.reduce((acc, key, i) => ({ ...acc, [key.toString()]: i + 1 }), {} as Dict<number>);

const CAYC_CONDITIONS_WITHOUT_FIXED_VALUE: CaycMetricKeys[] = [
const CAYC_CONDITIONS_WITHOUT_FIXED_VALUE: AllCaycMetricKeys[] = [
MetricKey.new_duplicated_lines_density,
MetricKey.new_coverage,
];
const CAYC_CONDITIONS_WITH_FIXED_VALUE: CaycMetricKeys[] = [
const CAYC_CONDITIONS_WITH_FIXED_VALUE: AllCaycMetricKeys[] = [
MetricKey.new_security_hotspots_reviewed,
MetricKey.new_violations,
MetricKey.new_reliability_rating,
MetricKey.new_security_rating,
MetricKey.new_maintainability_rating,
];

export function isConditionWithFixedValue(condition: Condition) {
return CAYC_CONDITIONS_WITH_FIXED_VALUE.includes(condition.metric as CaycMetricKeys);
return CAYC_CONDITIONS_WITH_FIXED_VALUE.includes(condition.metric as OptimizedCaycMetricKeys);
}

export function getCaycConditionMetadata(condition: Condition) {
const foundCondition = CAYC_CONDITIONS[condition.metric as CaycMetricKeys];
const foundCondition = OPTIMIZED_CAYC_CONDITIONS[condition.metric as OptimizedCaycMetricKeys];
return {
shouldRenderOperator: foundCondition?.shouldRenderOperator,
};
}

export function isQualityGateOptimized(qualityGate: QualityGate) {
return (
!qualityGate.isBuiltIn &&
qualityGate.caycStatus !== CaycStatus.NonCompliant &&
Object.values(OPTIMIZED_CAYC_CONDITIONS).every((condition) => {
const foundCondition = qualityGate.conditions?.find((c) => c.metric === condition.metric);
return (
foundCondition &&
!isWeakCondition(condition.metric as OptimizedCaycMetricKeys, foundCondition)
);
})
);
}

function isWeakCondition(key: AllCaycMetricKeys, selectedCondition: Condition) {
return (
!CAYC_CONDITIONS_WITHOUT_FIXED_VALUE.includes(key) &&
ALL_CAYC_CONDITIONS[key]?.error !== selectedCondition.error
);
}

export function getWeakMissingAndNonCaycConditions(conditions: Condition[]) {
const result: {
weakConditions: Condition[];
@@ -107,31 +183,18 @@ export function getWeakMissingAndNonCaycConditions(conditions: Condition[]) {
weakConditions: [],
missingConditions: [],
};
Object.keys(CAYC_CONDITIONS).forEach((key: CaycMetricKeys) => {
Object.keys(OPTIMIZED_CAYC_CONDITIONS).forEach((key: OptimizedCaycMetricKeys) => {
const selectedCondition = conditions.find((condition) => condition.metric === key);
if (!selectedCondition) {
result.missingConditions.push(CAYC_CONDITIONS[key]);
} else if (
!CAYC_CONDITIONS_WITHOUT_FIXED_VALUE.includes(key) &&
CAYC_CONDITIONS[key]?.error !== selectedCondition.error
) {
result.missingConditions.push(OPTIMIZED_CAYC_CONDITIONS[key]);
} else if (isWeakCondition(key, selectedCondition)) {
result.weakConditions.push(selectedCondition);
}
});
return result;
}

export function getCaycConditionsWithCorrectValue(conditions: Condition[]) {
return Object.keys(CAYC_CONDITIONS).map((key: CaycMetricKeys) => {
const selectedCondition = conditions.find((condition) => condition.metric === key);
if (CAYC_CONDITIONS_WITHOUT_FIXED_VALUE.includes(key) && selectedCondition) {
return selectedCondition;
}
return CAYC_CONDITIONS[key];
});
}

export function groupConditionsByMetric(
function groupConditionsByMetric(
conditions: Condition[],
isBuiltInQG = false,
): GroupedByMetricConditions {
@@ -183,11 +246,11 @@ export function groupAndSortByPriorityConditions(
}

export function getCorrectCaycCondition(condition: Condition) {
const conditionMetric = condition.metric as CaycMetricKeys;
const conditionMetric = condition.metric as OptimizedCaycMetricKeys;
if (CAYC_CONDITIONS_WITHOUT_FIXED_VALUE.includes(conditionMetric)) {
return condition;
}
return CAYC_CONDITIONS[conditionMetric];
return OPTIMIZED_CAYC_CONDITIONS[conditionMetric];
}

export function addCondition(qualityGate: QualityGate, condition: Condition): QualityGate {
@@ -225,32 +288,27 @@ export function replaceCondition(
return { ...qualityGate, conditions };
}

export function updateCaycCompliantStatus(conditions: Condition[]) {
if (conditions.length < Object.keys(CAYC_CONDITIONS).length) {
return CaycStatus.NonCompliant;
}

for (const key of Object.keys(CAYC_CONDITIONS)) {
const caycMetric = key as CaycMetricKeys;
const selectedCondition = conditions.find((condition) => condition.metric === key);
if (!selectedCondition) {
return CaycStatus.NonCompliant;
}

if (
!CAYC_CONDITIONS_WITHOUT_FIXED_VALUE.includes(caycMetric) &&
selectedCondition &&
selectedCondition.error !== CAYC_CONDITIONS[caycMetric].error
) {
return CaycStatus.NonCompliant;
}
}
function updateCaycCompliantStatus(conditions: Condition[]) {
const isCompliantOptimized = Object.values(OPTIMIZED_CAYC_CONDITIONS).every((condition) => {
const foundCondition = conditions.find((c) => c.metric === condition.metric);
return (
foundCondition &&
!isWeakCondition(condition.metric as OptimizedCaycMetricKeys, foundCondition)
);
});
const isCompliantUnoptimized = Object.values(UNOPTIMIZED_CAYC_CONDITIONS).every((condition) => {
const foundCondition = conditions.find((c) => c.metric === condition.metric);
return (
foundCondition &&
!isWeakCondition(condition.metric as UnoptimizedCaycMetricKeys, foundCondition)
);
});

if (conditions.length > Object.keys(CAYC_CONDITIONS).length) {
return CaycStatus.OverCompliant;
if (isCompliantOptimized || isCompliantUnoptimized) {
return CaycStatus.Compliant;
}

return CaycStatus.Compliant;
return CaycStatus.NonCompliant;
}

export function getPossibleOperators(metric: Metric) {

+ 6
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -2221,6 +2221,12 @@ quality_gates.cayc.review_update_modal.header=Fix "{0}" to comply with Clean as
quality_gates.cayc.review_update_modal.confirm_text=Fix Quality Gate
quality_gates.cayc.review_update_modal.description1=This quality gate will be updated to comply with {cayc_link}. Please review the changes below.
quality_gates.cayc.review_update_modal.description2=All other conditions will be preserved
quality_gates.cayc_optimize.banner.title=This quality gate can be further optimized for Clean as You Code
quality_gates.cayc_optimize.banner.description=This quality gate complies with the {cayc_link} methodology, but it can be further optimized to ensure that new code has 0 issues.
quality_gates.cayc_condition.review_optimize=Review and Optimize Quality Gate
quality_gates.cayc.review_optimize_modal.header=Optimize "{0}" for Clean as You Code
quality_gates.cayc.review_optimize_modal.confirm_text=Optimize Quality Gate
quality_gates.cayc.review_optimize_modal.description1=This quality gate will be optimized for {cayc_link}. Please review the changes below.
quality_gates.cayc.condition_simplification_tour.page_1.title='Clean as You Code' ready!
quality_gates.cayc.condition_simplification_tour.page_1.content1=The conditions in this quality gate have been updated to ensure that any code added or changed is clean.
quality_gates.cayc.condition_simplification_tour.page_2.title=One condition, zero issues

Loading…
Cancel
Save