mockQualityGate({ | mockQualityGate({ | ||||
name: 'SonarSource way - CFamily', | name: 'SonarSource way - CFamily', | ||||
conditions: [ | conditions: [ | ||||
{ | |||||
id: 'AXJMbIUHPAOIsUIE3eOi', | |||||
metric: 'new_security_hotspots_reviewed', | |||||
op: 'LT', | |||||
error: '85', | |||||
isCaycCondition: true, | |||||
}, | |||||
{ | { | ||||
id: 'AXJMbIUHPAOIsUIE3eOu', | id: 'AXJMbIUHPAOIsUIE3eOu', | ||||
metric: 'new_coverage', | metric: 'new_coverage', | ||||
isBuiltIn: false, | isBuiltIn: false, | ||||
caycStatus: CaycStatus.NonCompliant, | 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({ | mockQualityGate({ | ||||
name: 'Over Compliant CAYC QG', | name: 'Over Compliant CAYC QG', | ||||
conditions: [ | conditions: [ |
/* | |||||
* 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> | |||||
); | |||||
} |
/* | |||||
* 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> | |||||
); | |||||
} |
/* | |||||
* 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> | |||||
); | |||||
} |
onSaveCondition: (newCondition: Condition, oldCondition: Condition) => void; | onSaveCondition: (newCondition: Condition, oldCondition: Condition) => void; | ||||
lockEditing: () => void; | lockEditing: () => void; | ||||
qualityGate: QualityGate; | qualityGate: QualityGate; | ||||
isOptimizing?: boolean; | |||||
} | } | ||||
export default function CaycReviewUpdateConditionsModal(props: Readonly<Props>) { | export default function CaycReviewUpdateConditionsModal(props: Readonly<Props>) { | ||||
onAddCondition, | onAddCondition, | ||||
lockEditing, | lockEditing, | ||||
onClose, | onClose, | ||||
isOptimizing, | |||||
} = props; | } = props; | ||||
const { weakConditions, missingConditions } = getWeakMissingAndNonCaycConditions(conditions); | const { weakConditions, missingConditions } = getWeakMissingAndNonCaycConditions(conditions); | ||||
<div className="sw-mb-10"> | <div className="sw-mb-10"> | ||||
<SubHeading as="p" className="sw-body-sm"> | <SubHeading as="p" className="sw-body-sm"> | ||||
<FormattedMessage | <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={{ | values={{ | ||||
cayc_link: ( | cayc_link: ( | ||||
<Link to={getDocUrl('/user-guide/clean-as-you-code/')}> | <Link to={getDocUrl('/user-guide/clean-as-you-code/')}> | ||||
<Modal | <Modal | ||||
isLarge | isLarge | ||||
headerTitle={translateWithParameters( | 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, | qualityGate.name, | ||||
)} | )} | ||||
onClose={onClose} | onClose={onClose} | ||||
type="submit" | type="submit" | ||||
onClick={updateCaycQualityGate} | 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> | </ButtonPrimary> | ||||
} | } | ||||
secondaryButtonLabel={translate('close')} | secondaryButtonLabel={translate('close')} |
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
import * as React from 'react'; | 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 { 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 { Condition, Metric } from '../../../types/types'; | ||||
import { GreenColorText } from './ConditionValue'; | import { GreenColorText } from './ConditionValue'; | ||||
MetricKey.new_security_hotspots_reviewed, | MetricKey.new_security_hotspots_reviewed, | ||||
MetricKey.new_coverage, | MetricKey.new_coverage, | ||||
MetricKey.new_duplicated_lines_density, | MetricKey.new_duplicated_lines_density, | ||||
MetricKey.new_reliability_rating, | |||||
MetricKey.new_security_rating, | |||||
MetricKey.new_maintainability_rating, | |||||
]; | ]; | ||||
interface Props { | interface Props { | ||||
appState: AppState; | |||||
condition: Condition; | condition: Condition; | ||||
metric: Metric; | metric: Metric; | ||||
isToBeModified?: boolean; | isToBeModified?: boolean; | ||||
} | } | ||||
function ConditionValueDescription({ | |||||
export default function ConditionValueDescription({ | |||||
condition, | condition, | ||||
appState: { settings }, | |||||
metric, | metric, | ||||
isToBeModified = false, | isToBeModified = false, | ||||
}: Readonly<Props>) { | }: 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 ( | return ( | ||||
<GreenColorText isToBeModified={isToBeModified}> | <GreenColorText isToBeModified={isToBeModified}> | ||||
{condition.isCaycCondition && | {condition.isCaycCondition && | ||||
</GreenColorText> | </GreenColorText> | ||||
); | ); | ||||
} | } | ||||
export default withAppStateContext(ConditionValueDescription); |
*/ | */ | ||||
import { | import { | ||||
ButtonPrimary, | |||||
ButtonSecondary, | ButtonSecondary, | ||||
CardWithPrimaryBackground, | |||||
FlagMessage, | FlagMessage, | ||||
HeadingDark, | HeadingDark, | ||||
HelperHintIcon, | HelperHintIcon, | ||||
Link, | Link, | ||||
Note, | Note, | ||||
SubHeading, | SubHeading, | ||||
SubHeadingHighlight, | |||||
} from 'design-system'; | } from 'design-system'; | ||||
import { differenceWith, map, uniqBy } from 'lodash'; | import { differenceWith, map, uniqBy } from 'lodash'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
Metric, | Metric, | ||||
QualityGate, | QualityGate, | ||||
} from '../../../types/types'; | } from '../../../types/types'; | ||||
import { CAYC_CONDITIONS, groupAndSortByPriorityConditions } from '../utils'; | |||||
import { groupAndSortByPriorityConditions, isQualityGateOptimized } from '../utils'; | |||||
import CaYCConditionsSimplificationGuide from './CaYCConditionsSimplificationGuide'; | import CaYCConditionsSimplificationGuide from './CaYCConditionsSimplificationGuide'; | ||||
import CaycConditionsListItem from './CaycConditionsListItem'; | |||||
import CaycCompliantBanner from './CaycCompliantBanner'; | |||||
import CaycConditionsTable from './CaycConditionsTable'; | import CaycConditionsTable from './CaycConditionsTable'; | ||||
import CaycFixOptimizeBanner from './CaycFixOptimizeBanner'; | |||||
import ConditionModal from './ConditionModal'; | import ConditionModal from './ConditionModal'; | ||||
import CaycReviewUpdateConditionsModal from './ConditionReviewAndUpdateModal'; | import CaycReviewUpdateConditionsModal from './ConditionReviewAndUpdateModal'; | ||||
import ConditionsTable from './ConditionsTable'; | import ConditionsTable from './ConditionsTable'; | ||||
MetricKey.new_security_hotspots, | MetricKey.new_security_hotspots, | ||||
]; | ]; | ||||
export function Conditions({ | |||||
function Conditions({ | |||||
qualityGate, | qualityGate, | ||||
metrics, | metrics, | ||||
onRemoveCondition, | onRemoveCondition, | ||||
metric: metrics[condition.metric], | metric: metrics[condition.metric], | ||||
})); | })); | ||||
const getDocUrl = useDocUrl(); | |||||
// set edit only when the name is change | // set edit only when the name is change | ||||
// i.e when user changes the quality gate | // i.e when user changes the quality gate | ||||
React.useEffect(() => { | React.useEffect(() => { | ||||
[metrics, qualityGate, onAddCondition], | [metrics, qualityGate, onAddCondition], | ||||
); | ); | ||||
const getDocUrl = useDocUrl(); | |||||
const isCompliantCustomQualityGate = | |||||
qualityGate.caycStatus !== CaycStatus.NonCompliant && !qualityGate.isBuiltIn; | |||||
const isOptimizing = isCompliantCustomQualityGate && !isQualityGateOptimized(qualityGate); | |||||
const renderCaycModal = React.useCallback( | const renderCaycModal = React.useCallback( | ||||
({ onClose }: ModalProps) => { | ({ onClose }: ModalProps) => { | ||||
const { conditions = [] } = qualityGate; | const { conditions = [] } = qualityGate; | ||||
conditions={conditions} | conditions={conditions} | ||||
scope="new-cayc" | scope="new-cayc" | ||||
onClose={onClose} | onClose={onClose} | ||||
isOptimizing={isOptimizing} | |||||
/> | /> | ||||
); | ); | ||||
}, | }, | ||||
[qualityGate, metrics, updatedConditionId, onAddCondition, onRemoveCondition, onSaveCondition], | |||||
[ | |||||
qualityGate, | |||||
metrics, | |||||
updatedConditionId, | |||||
onAddCondition, | |||||
onRemoveCondition, | |||||
onSaveCondition, | |||||
isOptimizing, | |||||
], | |||||
); | ); | ||||
return ( | return ( | ||||
<div> | <div> | ||||
<CaYCConditionsSimplificationGuide /> | <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 && ( | {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"> | <header className="sw-flex sw-items-center sw-mb-4 sw-justify-between"> |
expect(overallConditionsWrapper.getByText('Complexity / Function')).toBeInTheDocument(); | 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 () => { | 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(); | const user = userEvent.setup(); | ||||
renderQualityGateApp(); | renderQualityGateApp(); | ||||
expect(screen.queryByText('quality_gates.cayc.tooltip.message')).not.toBeInTheDocument(); | 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 () => { | it('should warn user when quality gate is not CAYC compliant and user has permission to edit it', async () => { | ||||
const user = userEvent.setup(); | const user = userEvent.setup(); | ||||
qualityGateHandler.setIsAdmin(true); | qualityGateHandler.setIsAdmin(true); | ||||
const nonCompliantQualityGate = await screen.findByRole('button', { name: /Non Cayc QG/ }); | const nonCompliantQualityGate = await screen.findByRole('button', { name: /Non Cayc QG/ }); | ||||
await user.click(nonCompliantQualityGate); | await user.click(nonCompliantQualityGate); | ||||
// expect(screen.getByTestId('conditions')).toMatchSnapshot(); | |||||
expect(await screen.findByText(/quality_gates.cayc_missing.banner.title/)).toBeInTheDocument(); | expect(await screen.findByText(/quality_gates.cayc_missing.banner.title/)).toBeInTheDocument(); | ||||
expect(screen.getAllByText('quality_gates.cayc.tooltip.message').length).toBeGreaterThan(0); | 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 () => { | it('should render CaYC conditions on a separate table if Sonar way', async () => { | ||||
const user = userEvent.setup(); | const user = userEvent.setup(); | ||||
qualityGateHandler.setIsAdmin(true); | qualityGateHandler.setIsAdmin(true); |
caycConditions: Condition[]; | caycConditions: Condition[]; | ||||
} | } | ||||
type CaycMetricKeys = | |||||
| MetricKey.new_violations | |||||
type CommonCaycMetricKeys = | |||||
| MetricKey.new_security_hotspots_reviewed | | MetricKey.new_security_hotspots_reviewed | ||||
| MetricKey.new_coverage | | MetricKey.new_coverage | ||||
| MetricKey.new_duplicated_lines_density; | | 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 } | 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]: { | [MetricKey.new_security_hotspots_reviewed]: { | ||||
id: MetricKey.new_security_hotspots_reviewed, | id: MetricKey.new_security_hotspots_reviewed, | ||||
metric: MetricKey.new_security_hotspots_reviewed, | metric: MetricKey.new_security_hotspots_reviewed, | ||||
}, | }, | ||||
}; | }; | ||||
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> = [ | const CAYC_CONDITION_ORDER_PRIORITIES: Dict<number> = [ | ||||
MetricKey.new_violations, | MetricKey.new_violations, | ||||
MetricKey.new_security_hotspots_reviewed, | MetricKey.new_security_hotspots_reviewed, | ||||
.reverse() | .reverse() | ||||
.reduce((acc, key, i) => ({ ...acc, [key.toString()]: i + 1 }), {} as Dict<number>); | .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_duplicated_lines_density, | ||||
MetricKey.new_coverage, | MetricKey.new_coverage, | ||||
]; | ]; | ||||
const CAYC_CONDITIONS_WITH_FIXED_VALUE: CaycMetricKeys[] = [ | |||||
const CAYC_CONDITIONS_WITH_FIXED_VALUE: AllCaycMetricKeys[] = [ | |||||
MetricKey.new_security_hotspots_reviewed, | MetricKey.new_security_hotspots_reviewed, | ||||
MetricKey.new_violations, | MetricKey.new_violations, | ||||
MetricKey.new_reliability_rating, | |||||
MetricKey.new_security_rating, | |||||
MetricKey.new_maintainability_rating, | |||||
]; | ]; | ||||
export function isConditionWithFixedValue(condition: Condition) { | 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) { | export function getCaycConditionMetadata(condition: Condition) { | ||||
const foundCondition = CAYC_CONDITIONS[condition.metric as CaycMetricKeys]; | |||||
const foundCondition = OPTIMIZED_CAYC_CONDITIONS[condition.metric as OptimizedCaycMetricKeys]; | |||||
return { | return { | ||||
shouldRenderOperator: foundCondition?.shouldRenderOperator, | 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[]) { | export function getWeakMissingAndNonCaycConditions(conditions: Condition[]) { | ||||
const result: { | const result: { | ||||
weakConditions: Condition[]; | weakConditions: Condition[]; | ||||
weakConditions: [], | weakConditions: [], | ||||
missingConditions: [], | missingConditions: [], | ||||
}; | }; | ||||
Object.keys(CAYC_CONDITIONS).forEach((key: CaycMetricKeys) => { | |||||
Object.keys(OPTIMIZED_CAYC_CONDITIONS).forEach((key: OptimizedCaycMetricKeys) => { | |||||
const selectedCondition = conditions.find((condition) => condition.metric === key); | const selectedCondition = conditions.find((condition) => condition.metric === key); | ||||
if (!selectedCondition) { | 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); | result.weakConditions.push(selectedCondition); | ||||
} | } | ||||
}); | }); | ||||
return result; | 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[], | conditions: Condition[], | ||||
isBuiltInQG = false, | isBuiltInQG = false, | ||||
): GroupedByMetricConditions { | ): GroupedByMetricConditions { | ||||
} | } | ||||
export function getCorrectCaycCondition(condition: Condition) { | 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)) { | if (CAYC_CONDITIONS_WITHOUT_FIXED_VALUE.includes(conditionMetric)) { | ||||
return condition; | return condition; | ||||
} | } | ||||
return CAYC_CONDITIONS[conditionMetric]; | |||||
return OPTIMIZED_CAYC_CONDITIONS[conditionMetric]; | |||||
} | } | ||||
export function addCondition(qualityGate: QualityGate, condition: Condition): QualityGate { | export function addCondition(qualityGate: QualityGate, condition: Condition): QualityGate { | ||||
return { ...qualityGate, conditions }; | 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) { | export function getPossibleOperators(metric: Metric) { |
quality_gates.cayc.review_update_modal.confirm_text=Fix Quality Gate | 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.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.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.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_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 | quality_gates.cayc.condition_simplification_tour.page_2.title=One condition, zero issues |