From d8b7397bfb96f0369c61f5430d4292de38f4ec1e Mon Sep 17 00:00:00 2001 From: Andrey Luiz Date: Tue, 23 May 2023 15:52:57 +0200 Subject: [PATCH] SONAR-19332 Warn and prevent parent NCD option selection if not compliant (#8326) --- .../components/BranchListRow.tsx | 11 ++++- .../components/ProjectBaselineApp.tsx | 1 + .../components/ProjectBaselineSelector.tsx | 45 ++++++++++++++++++- .../__tests__/ProjectBaselineApp-it.tsx | 38 +++++++++++++++- .../components/controls/ActionsDropdown.tsx | 2 + .../main/js/components/controls/buttons.css | 5 +++ .../resources/org/sonar/l10n/core.properties | 8 +++- 7 files changed, 106 insertions(+), 4 deletions(-) diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchListRow.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchListRow.tsx index 0560401c2a1..a386e12a2d1 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchListRow.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchListRow.tsx @@ -26,6 +26,7 @@ import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { BranchWithNewCodePeriod } from '../../../types/branch-like'; import { NewCodePeriod, NewCodePeriodSettingType } from '../../../types/types'; +import { isNewCodeDefinitionCompliant } from '../../../helpers/periods'; export interface BranchListRowProps { branch: BranchWithNewCodePeriod; @@ -98,6 +99,8 @@ export default function BranchListRow(props: BranchListRowProps) { ); } + const isCompliant = isNewCodeDefinitionCompliant(inheritedSetting); + return ( @@ -125,7 +128,13 @@ export default function BranchListRow(props: BranchListRowProps) { {translate('edit')} {branch.newCodePeriod && ( - props.onResetToDefault(branch.name)}> + props.onResetToDefault(branch.name)} + tooltipOverlay={ + isCompliant ? null : translate('project_baseline.compliance.warning.title.project') + } + > {translate('reset_to_default')} )} diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineApp.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineApp.tsx index 5f7925f1149..0b3f8674abd 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineApp.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineApp.tsx @@ -281,6 +281,7 @@ class ProjectBaselineApp extends React.PureComponent { branch={branchLike} branchList={branchList} branchesEnabled={branchSupportEnabled} + canAdmin={appState.canAdmin} component={component.key} currentSetting={currentSetting} currentSettingValue={currentSettingValue} diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineSelector.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineSelector.tsx index 136f93cbabd..8c38d5b16e6 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineSelector.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineSelector.tsx @@ -33,12 +33,17 @@ import BaselineSettingDays, { BaselineSettingDaysSettingLevel } from './Baseline import BaselineSettingPreviousVersion from './BaselineSettingPreviousVersion'; import BaselineSettingReferenceBranch from './BaselineSettingReferenceBranch'; import BranchAnalysisList from './BranchAnalysisList'; +import { isNewCodeDefinitionCompliant } from '../../../helpers/periods'; +import Tooltip from '../../../components/controls/Tooltip'; +import { FormattedMessage } from 'react-intl'; +import Link from '../../../components/common/Link'; export interface ProjectBaselineSelectorProps { analysis?: string; branch: Branch; branchList: Branch[]; branchesEnabled?: boolean; + canAdmin: boolean | undefined; component: string; currentSetting?: NewCodePeriodSettingType; currentSettingValue?: string; @@ -94,6 +99,7 @@ export default function ProjectBaselineSelector(props: ProjectBaselineSelectorPr branch, branchList, branchesEnabled, + canAdmin, component, currentSetting, currentSettingValue, @@ -105,6 +111,8 @@ export default function ProjectBaselineSelector(props: ProjectBaselineSelectorPr selected, } = props; + const isGeneralSettingCompliant = isNewCodeDefinitionCompliant(generalSetting); + const { isChanged, isValid } = validateSetting({ analysis, currentSetting, @@ -121,12 +129,47 @@ export default function ProjectBaselineSelector(props: ProjectBaselineSelectorPr props.onToggleSpecificSetting(false)} value="general" > - {translate('project_baseline.general_setting')} + + {translate('project_baseline.global_setting')} +
{renderGeneralSetting(generalSetting)}
+ {!isGeneralSettingCompliant && ( + +

+ {translate('project_baseline.compliance.warning.title.global')} +

+

+ {canAdmin ? ( + + {translate('project_baseline.warning.explanation.action.admin.link')} + + ), + }} + /> + ) : ( + translate('project_baseline.compliance.warning.explanation') + )} +

+
+ )} { expect(ui.referenceBranchRadio.query()).not.toBeInTheDocument(); }); +it('prevents selection of global setting if it is not compliant and warns non-admin about it', async () => { + codePeriodsMock.setNewCodePeriod({ + type: NewCodePeriodSettingType.NUMBER_OF_DAYS, + value: '99', + inherited: true, + }); + + const { ui } = getPageObjects(); + renderProjectBaselineApp(); + await ui.appIsLoaded(); + + expect(ui.generalSettingRadio.get()).toBeChecked(); + expect(ui.generalSettingRadio.get()).toHaveClass('disabled'); + expect(ui.complianceWarning.get()).toBeVisible(); +}); + +it('prevents selection of global setting if it is not compliant and warns admin about it', async () => { + codePeriodsMock.setNewCodePeriod({ + type: NewCodePeriodSettingType.NUMBER_OF_DAYS, + value: '99', + inherited: true, + }); + + const { ui } = getPageObjects(); + renderProjectBaselineApp({ appState: mockAppState({ canAdmin: true }) }); + await ui.appIsLoaded(); + + expect(ui.generalSettingRadio.get()).toBeChecked(); + expect(ui.generalSettingRadio.get()).toHaveClass('disabled'); + expect(ui.complianceWarningAdmin.get()).toBeVisible(); + expect(ui.complianceWarning.query()).not.toBeInTheDocument(); +}); + it('renders correctly with branch support feature', async () => { const { ui } = getPageObjects(); renderProjectBaselineApp({ @@ -228,7 +262,7 @@ function getPageObjects() { pageHeading: byRole('heading', { name: 'project_baseline.page' }), branchListHeading: byRole('heading', { name: 'project_baseline.default_setting' }), generalSettingsLink: byRole('link', { name: 'project_baseline.page.description2.link' }), - generalSettingRadio: byRole('radio', { name: 'project_baseline.general_setting' }), + generalSettingRadio: byRole('radio', { name: 'project_baseline.global_setting' }), specificSettingRadio: byRole('radio', { name: 'project_baseline.specific_setting' }), previousVersionRadio: byRole('radio', { name: /baseline.previous_version.description/ }), numberDaysRadio: byRole('radio', { name: /baseline.number_days.description/ }), @@ -245,6 +279,8 @@ function getPageObjects() { editButton: byRole('button', { name: 'edit' }), resetToDefaultButton: byRole('button', { name: 'reset_to_default' }), saved: byText('settings.state.saved'), + complianceWarningAdmin: byText('project_baseline.compliance.warning.explanation.admin'), + complianceWarning: byText('project_baseline.compliance.warning.explanation'), }; async function appIsLoaded() { diff --git a/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx b/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx index b01909c75ac..480ebf1ecea 100644 --- a/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx +++ b/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx @@ -66,6 +66,7 @@ interface ItemProps { className?: string; children: React.ReactNode; destructive?: boolean; + disabled?: boolean; label?: string; tooltipOverlay?: React.ReactNode; tooltipPlacement?: Placement; @@ -114,6 +115,7 @@ export class ActionsDropdownItem extends React.PureComponent { children = ( New Code + baseline.previous_version=Previous version baseline.previous_version.usecase=Recommended for projects following regular versions or releases. baseline.previous_version.description=Any code that has changed since the previous version is considered new code. -- 2.39.5