From 57337179623af31093cd2b99c485ad7eb326abd1 Mon Sep 17 00:00:00 2001 From: Andrey Luiz Date: Wed, 23 Aug 2023 10:23:09 +0200 Subject: [PATCH] SONAR-20156 Display banner on the project NCD page to notify project admins about project NCD change Co-authored-by: Ambroise C --- server/sonar-web/src/main/js/api/messages.ts | 1 + .../src/main/js/app/components/App.tsx | 2 - .../js/app/components/GlobalContainer.tsx | 2 + .../__tests__/__snapshots__/App-test.tsx.snap | 2 - .../projectNewCode/components/BranchList.tsx | 6 +- .../BranchNewCodeDefinitionSettingModal.tsx | 52 ++++-- ...ewCodeDefinitionSettingReferenceBranch.tsx | 8 +- .../ProjectNewCodeDefinitionApp.tsx | 165 +++++++++++------- .../ProjectNewCodeDefinitionSelector.tsx | 83 +++++---- .../components/__tests__/utils-test.ts | 48 ++--- .../src/main/js/apps/projectNewCode/utils.ts | 30 ++-- .../settings/components/NewCodeDefinition.tsx | 17 +- .../__tests__/NewCodeDefinition-it.tsx | 2 +- .../NCDAutoUpdateMessage.tsx | 7 +- .../NewCodeDefinitionDaysOption.tsx | 87 +++++---- .../NewCodeDefinitionSelector.tsx | 2 + .../NewCodeDefinitionWarning.tsx | 7 +- .../components/new-code-definition/utils.ts | 7 + .../ui/DismissableAlertComponent.tsx | 9 +- .../resources/org/sonar/l10n/core.properties | 2 +- 20 files changed, 338 insertions(+), 201 deletions(-) diff --git a/server/sonar-web/src/main/js/api/messages.ts b/server/sonar-web/src/main/js/api/messages.ts index c27e726bbc7..6269e0e0eab 100644 --- a/server/sonar-web/src/main/js/api/messages.ts +++ b/server/sonar-web/src/main/js/api/messages.ts @@ -24,6 +24,7 @@ export enum MessageTypes { GlobalNcd90 = 'GLOBAL_NCD_90', GlobalNcdPage90 = 'GLOBAL_NCD_PAGE_90', ProjectNcd90 = 'PROJECT_NCD_90', + ProjectNcdPage90 = 'PROJECT_NCD_PAGE_90', BranchNcd90 = 'BRANCH_NCD_90', } diff --git a/server/sonar-web/src/main/js/app/components/App.tsx b/server/sonar-web/src/main/js/app/components/App.tsx index 46aa47ac7a7..f8a15634a8c 100644 --- a/server/sonar-web/src/main/js/app/components/App.tsx +++ b/server/sonar-web/src/main/js/app/components/App.tsx @@ -19,7 +19,6 @@ */ import * as React from 'react'; import { Outlet } from 'react-router-dom'; -import NCDAutoUpdateMessage from '../../components/new-code-definition/NCDAutoUpdateMessage'; import { AppState } from '../../types/appstate'; import { GlobalSettingKeys } from '../../types/settings'; import KeyboardShortcutsModal from './KeyboardShortcutsModal'; @@ -90,7 +89,6 @@ export class App extends React.PureComponent { render() { return ( <> - {this.renderPreconnectLink()} diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx index 2346d06ccac..5fba3cf935c 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx @@ -25,6 +25,7 @@ import { Outlet, useLocation } from 'react-router-dom'; import A11yProvider from '../../components/a11y/A11yProvider'; import A11ySkipLinks from '../../components/a11y/A11ySkipLinks'; import SuggestionsProvider from '../../components/embed-docs-modal/SuggestionsProvider'; +import NCDAutoUpdateMessage from '../../components/new-code-definition/NCDAutoUpdateMessage'; import Workspace from '../../components/workspace/Workspace'; import GlobalFooter from './GlobalFooter'; import StartupModal from './StartupModal'; @@ -80,6 +81,7 @@ export default function GlobalContainer() {
+
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/App-test.tsx.snap index 88271399b3b..1937a131c98 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/App-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/App-test.tsx.snap @@ -2,7 +2,6 @@ exports[`should render correctly: default 1`] = ` - @@ -11,7 +10,6 @@ exports[`should render correctly: default 1`] = ` exports[`should render correctly: with gravatar 1`] = ` - { }; render() { - const { branchList, inheritedSetting, generalSetting } = this.props; + const { branchList, inheritedSetting, globalNewCodeDefinition } = this.props; const { branches, editedBranch, loading } = this.state; if (branches.length < 1) { @@ -179,7 +179,7 @@ export default class BranchList extends React.PureComponent { component={this.props.component.key} onClose={this.closeEditModal} inheritedSetting={inheritedSetting} - generalSetting={generalSetting} + globalNewCodeDefinition={globalNewCodeDefinition} /> )} diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchNewCodeDefinitionSettingModal.tsx b/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchNewCodeDefinitionSettingModal.tsx index 06d4c0571ff..d5b8bc266e6 100644 --- a/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchNewCodeDefinitionSettingModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchNewCodeDefinitionSettingModal.tsx @@ -25,6 +25,7 @@ import { ResetButtonLink, SubmitButton } from '../../../components/controls/butt import NewCodeDefinitionDaysOption from '../../../components/new-code-definition/NewCodeDefinitionDaysOption'; import NewCodeDefinitionPreviousVersionOption from '../../../components/new-code-definition/NewCodeDefinitionPreviousVersionOption'; import NewCodeDefinitionWarning from '../../../components/new-code-definition/NewCodeDefinitionWarning'; +import { NewCodeDefinitionLevels } from '../../../components/new-code-definition/utils'; import Spinner from '../../../components/ui/Spinner'; import { toISO8601WithOffsetString } from '../../../helpers/dates'; import { translate, translateWithParameters } from '../../../helpers/l10n'; @@ -42,7 +43,7 @@ interface Props { component: string; onClose: (branch?: string, newSetting?: NewCodeDefinition) => void; inheritedSetting: NewCodeDefinition; - generalSetting: NewCodeDefinition; + globalNewCodeDefinition: NewCodeDefinition; } interface State { @@ -52,7 +53,7 @@ interface State { isChanged: boolean; referenceBranch: string; saving: boolean; - selected?: NewCodeDefinitionType; + selectedNewCodeDefinitionType?: NewCodeDefinitionType; } export default class BranchNewCodeDefinitionSettingModal extends React.PureComponent { @@ -61,7 +62,7 @@ export default class BranchNewCodeDefinitionSettingModal extends React.PureCompo constructor(props: Props) { super(props); - const { branch, branchList, inheritedSetting, generalSetting } = props; + const { branch, branchList, inheritedSetting, globalNewCodeDefinition } = props; const otherBranches = branchList.filter((b) => b.name !== branch.name); const defaultBranch = otherBranches.length > 0 ? otherBranches[0].name : ''; @@ -69,12 +70,12 @@ export default class BranchNewCodeDefinitionSettingModal extends React.PureCompo analysis: this.getValueFromProps(NewCodeDefinitionType.SpecificAnalysis) || '', days: this.getValueFromProps(NewCodeDefinitionType.NumberOfDays) || - getNumberOfDaysDefaultValue(generalSetting, inheritedSetting), + getNumberOfDaysDefaultValue(globalNewCodeDefinition, inheritedSetting), isChanged: false, referenceBranch: this.getValueFromProps(NewCodeDefinitionType.ReferenceBranch) || defaultBranch, saving: false, - selected: branch.newCodePeriod?.type, + selectedNewCodeDefinitionType: branch.newCodePeriod?.type, }; } @@ -103,9 +104,15 @@ export default class BranchNewCodeDefinitionSettingModal extends React.PureCompo e.preventDefault(); const { branch, component } = this.props; - const { analysis, analysisDate, days, referenceBranch, selected: type } = this.state; + const { + analysis, + analysisDate, + days, + referenceBranch, + selectedNewCodeDefinitionType: type, + } = this.state; - const value = getSettingValue({ type, analysis, days, referenceBranch }); + const value = getSettingValue({ type, analysis, numberOfDays: days, referenceBranch }); if (type) { this.setState({ saving: true }); @@ -146,13 +153,17 @@ export default class BranchNewCodeDefinitionSettingModal extends React.PureCompo handleSelectReferenceBranch = (referenceBranch: string) => this.setState({ referenceBranch, isChanged: true }); - handleSelectSetting = (selected: NewCodeDefinitionType) => { - this.setState((currentState) => ({ selected, isChanged: selected !== currentState.selected })); + handleSelectSetting = (selectedNewCodeDefinitionType: NewCodeDefinitionType) => { + this.setState((currentState) => ({ + selectedNewCodeDefinitionType, + isChanged: selectedNewCodeDefinitionType !== currentState.selectedNewCodeDefinitionType, + })); }; render() { const { branch, branchList } = this.props; - const { analysis, days, isChanged, referenceBranch, saving, selected } = this.state; + const { analysis, days, isChanged, referenceBranch, saving, selectedNewCodeDefinitionType } = + this.state; const header = translateWithParameters('baseline.new_code_period_for_branch_x', branch.name); @@ -160,9 +171,9 @@ export default class BranchNewCodeDefinitionSettingModal extends React.PureCompo const currentSettingValue = branch.newCodePeriod?.value; const isValid = validateSetting({ - days, + numberOfDays: days, referenceBranch, - selected, + selectedNewCodeDefinitionType, }); return ( @@ -177,13 +188,13 @@ export default class BranchNewCodeDefinitionSettingModal extends React.PureCompo newCodeDefinitionType={currentSetting} newCodeDefinitionValue={currentSettingValue} isBranchSupportEnabled - level="branch" + level={NewCodeDefinitionLevels.Branch} />
{currentSetting === NewCodeDefinitionType.SpecificAnalysis && ( )}
- {selected === NewCodeDefinitionType.SpecificAnalysis && ( + {selectedNewCodeDefinitionType === NewCodeDefinitionType.SpecificAnalysis && ( void; referenceBranch: string; selected: boolean; - settingLevel: 'project' | 'branch'; + settingLevel: Exclude< + NewCodeDefinitionLevels, + NewCodeDefinitionLevels.NewProject | NewCodeDefinitionLevels.Global + >; } export interface BranchOption { @@ -110,7 +114,7 @@ export default function NewCodeDefinitionSettingReferenceBranch( {selected && ( <> - {settingLevel === 'project' && ( + {settingLevel === NewCodeDefinitionLevels.Project && (

{translate('baseline.reference_branch.description2')}

)}
diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/components/ProjectNewCodeDefinitionApp.tsx b/server/sonar-web/src/main/js/apps/projectNewCode/components/ProjectNewCodeDefinitionApp.tsx index 92c1cbbe901..ad353f95ca3 100644 --- a/server/sonar-web/src/main/js/apps/projectNewCode/components/ProjectNewCodeDefinitionApp.tsx +++ b/server/sonar-web/src/main/js/apps/projectNewCode/components/ProjectNewCodeDefinitionApp.tsx @@ -62,16 +62,18 @@ interface Props extends WithAvailableFeaturesProps { interface State { analysis?: string; branchList: Branch[]; - currentSetting?: NewCodeDefinitionType; - currentSettingValue?: string; - days: string; - generalSetting?: NewCodeDefinition; + newCodeDefinitionType?: NewCodeDefinitionType; + newCodeDefinitionValue?: string; + previousNonCompliantValue?: string; + projectNcdUpdatedAt?: number; + numberOfDays: string; + globalNewCodeDefinition?: NewCodeDefinition; isChanged: boolean; loading: boolean; - overrideGeneralSetting?: boolean; + overrideGlobalNewCodeDefinition?: boolean; referenceBranch?: string; saving: boolean; - selected?: NewCodeDefinitionType; + selectedNewCodeDefinitionType?: NewCodeDefinitionType; success?: boolean; } @@ -79,7 +81,7 @@ class ProjectNewCodeDefinitionApp extends React.PureComponent { mounted = false; state: State = { branchList: [], - days: getNumberOfDaysDefaultValue(), + numberOfDays: getNumberOfDaysDefaultValue(), isChanged: false, loading: true, saving: false, @@ -105,30 +107,43 @@ class ProjectNewCodeDefinitionApp extends React.PureComponent { } getUpdatedState(params: { - currentSetting?: NewCodeDefinitionType; - currentSettingValue?: string; - generalSetting: NewCodeDefinition; + newCodeDefinitionType?: NewCodeDefinitionType; + newCodeDefinitionValue?: string; + globalNewCodeDefinition: NewCodeDefinition; + previousNonCompliantValue?: string; + projectNcdUpdatedAt?: number; }) { - const { currentSetting, currentSettingValue, generalSetting } = params; + const { + newCodeDefinitionType, + newCodeDefinitionValue, + globalNewCodeDefinition, + previousNonCompliantValue, + projectNcdUpdatedAt, + } = params; const { referenceBranch } = this.state; - const defaultDays = getNumberOfDaysDefaultValue(generalSetting); + const defaultDays = getNumberOfDaysDefaultValue(globalNewCodeDefinition); return { loading: false, - currentSetting, - currentSettingValue, - generalSetting, + newCodeDefinitionType, + newCodeDefinitionValue, + previousNonCompliantValue, + projectNcdUpdatedAt, + globalNewCodeDefinition, isChanged: false, - selected: currentSetting || generalSetting.type, - overrideGeneralSetting: Boolean(currentSetting), - days: - (currentSetting === NewCodeDefinitionType.NumberOfDays && currentSettingValue) || + selectedNewCodeDefinitionType: newCodeDefinitionType ?? globalNewCodeDefinition.type, + overrideGlobalNewCodeDefinition: Boolean(newCodeDefinitionType), + numberOfDays: + (newCodeDefinitionType === NewCodeDefinitionType.NumberOfDays && newCodeDefinitionValue) || defaultDays, analysis: - (currentSetting === NewCodeDefinitionType.SpecificAnalysis && currentSettingValue) || '', + (newCodeDefinitionType === NewCodeDefinitionType.SpecificAnalysis && + newCodeDefinitionValue) || + '', referenceBranch: - (currentSetting === NewCodeDefinitionType.ReferenceBranch && currentSettingValue) || + (newCodeDefinitionType === NewCodeDefinitionType.ReferenceBranch && + newCodeDefinitionValue) || referenceBranch, }; } @@ -150,21 +165,23 @@ class ProjectNewCodeDefinitionApp extends React.PureComponent { project: component.key, }), ]).then( - ([generalSetting, setting]) => { + ([globalNewCodeDefinition, setting]) => { if (this.mounted) { - if (!generalSetting.type) { - generalSetting = { type: DEFAULT_NEW_CODE_DEFINITION_TYPE }; + if (!globalNewCodeDefinition.type) { + globalNewCodeDefinition = { type: DEFAULT_NEW_CODE_DEFINITION_TYPE }; } - const currentSettingValue = setting.value; - const currentSetting = setting.inherited + const newCodeDefinitionValue = setting.value; + const newCodeDefinitionType = setting.inherited ? undefined : setting.type || DEFAULT_NEW_CODE_DEFINITION_TYPE; this.setState( this.getUpdatedState({ - generalSetting, - currentSetting, - currentSettingValue, + globalNewCodeDefinition, + newCodeDefinitionType, + newCodeDefinitionValue, + previousNonCompliantValue: setting.previousNonCompliantValue, + projectNcdUpdatedAt: setting.updatedAt, }) ); } @@ -181,9 +198,9 @@ class ProjectNewCodeDefinitionApp extends React.PureComponent { () => { this.setState({ saving: false, - currentSetting: undefined, + newCodeDefinitionType: undefined, isChanged: false, - selected: undefined, + selectedNewCodeDefinitionType: undefined, success: true, }); this.resetSuccess(); @@ -194,7 +211,7 @@ class ProjectNewCodeDefinitionApp extends React.PureComponent { ); }; - handleSelectDays = (days: string) => this.setState({ days, isChanged: true }); + handleSelectDays = (days: string) => this.setState({ numberOfDays: days, isChanged: true }); handleSelectReferenceBranch = (referenceBranch: string) => { this.setState({ referenceBranch, isChanged: true }); @@ -203,37 +220,47 @@ class ProjectNewCodeDefinitionApp extends React.PureComponent { handleCancel = () => this.setState( ({ - generalSetting = { type: DEFAULT_NEW_CODE_DEFINITION_TYPE }, - currentSetting, - currentSettingValue, - }) => this.getUpdatedState({ generalSetting, currentSetting, currentSettingValue }) + globalNewCodeDefinition = { type: DEFAULT_NEW_CODE_DEFINITION_TYPE }, + newCodeDefinitionType, + newCodeDefinitionValue, + }) => + this.getUpdatedState({ + globalNewCodeDefinition, + newCodeDefinitionType, + newCodeDefinitionValue, + }) ); - handleSelectSetting = (selected?: NewCodeDefinitionType) => { + handleSelectSetting = (selectedNewCodeDefinitionType?: NewCodeDefinitionType) => { this.setState((currentState) => ({ - selected, - isChanged: selected !== currentState.selected, + selectedNewCodeDefinitionType, + isChanged: selectedNewCodeDefinitionType !== currentState.selectedNewCodeDefinitionType, })); }; - handleToggleSpecificSetting = (overrideGeneralSetting: boolean) => + handleToggleSpecificSetting = (overrideGlobalNewCodeDefinition: boolean) => this.setState((currentState) => ({ - overrideGeneralSetting, - isChanged: currentState.overrideGeneralSetting !== overrideGeneralSetting, + overrideGlobalNewCodeDefinition, + isChanged: currentState.overrideGlobalNewCodeDefinition !== overrideGlobalNewCodeDefinition, })); handleSubmit = (e: React.SyntheticEvent) => { e.preventDefault(); const { component } = this.props; - const { days, selected: type, referenceBranch, overrideGeneralSetting } = this.state; + const { + numberOfDays, + selectedNewCodeDefinitionType: type, + referenceBranch, + overrideGlobalNewCodeDefinition, + } = this.state; - if (!overrideGeneralSetting) { + if (!overrideGlobalNewCodeDefinition) { this.resetSetting(); return; } - const value = getSettingValue({ type, days, referenceBranch }); + const value = getSettingValue({ type, numberOfDays, referenceBranch }); if (type) { this.setState({ saving: true }); @@ -245,8 +272,10 @@ class ProjectNewCodeDefinitionApp extends React.PureComponent { () => { this.setState({ saving: false, - currentSetting: type, - currentSettingValue: value || undefined, + newCodeDefinitionType: type, + newCodeDefinitionValue: value || undefined, + previousNonCompliantValue: undefined, + projectNcdUpdatedAt: Date.now(), isChanged: false, success: true, }); @@ -264,16 +293,18 @@ class ProjectNewCodeDefinitionApp extends React.PureComponent { const { analysis, branchList, - currentSetting, - days, - generalSetting, + newCodeDefinitionType, + numberOfDays, + previousNonCompliantValue, + projectNcdUpdatedAt, + globalNewCodeDefinition, isChanged, loading, - currentSettingValue, - overrideGeneralSetting, + newCodeDefinitionValue, + overrideGlobalNewCodeDefinition, referenceBranch, saving, - selected, + selectedNewCodeDefinitionType, success, } = this.state; const branchSupportEnabled = this.props.hasFeature(Feature.BranchSupport); @@ -290,7 +321,7 @@ class ProjectNewCodeDefinitionApp extends React.PureComponent {
{branchSupportEnabled &&

{translate('project_baseline.default_setting')}

} - {generalSetting && overrideGeneralSetting !== undefined && ( + {globalNewCodeDefinition && overrideGlobalNewCodeDefinition !== undefined && ( { branchesEnabled={branchSupportEnabled} canAdmin={appState.canAdmin} component={component.key} - currentSetting={currentSetting} - currentSettingValue={currentSettingValue} - days={days} - generalSetting={generalSetting} + newCodeDefinitionType={newCodeDefinitionType} + newCodeDefinitionValue={newCodeDefinitionValue} + days={numberOfDays} + previousNonCompliantValue={previousNonCompliantValue} + projectNcdUpdatedAt={projectNcdUpdatedAt} + globalNewCodeDefinition={globalNewCodeDefinition} isChanged={isChanged} onCancel={this.handleCancel} onSelectDays={this.handleSelectDays} @@ -309,10 +342,10 @@ class ProjectNewCodeDefinitionApp extends React.PureComponent { onSelectSetting={this.handleSelectSetting} onSubmit={this.handleSubmit} onToggleSpecificSetting={this.handleToggleSpecificSetting} - overrideGeneralSetting={overrideGeneralSetting} + overrideGlobalNewCodeDefinition={overrideGlobalNewCodeDefinition} referenceBranch={referenceBranch} saving={saving} - selected={selected} + selectedNewCodeDefinitionType={selectedNewCodeDefinitionType} /> )} @@ -322,7 +355,7 @@ class ProjectNewCodeDefinitionApp extends React.PureComponent { {translate('settings.state.saved')}
- {generalSetting && branchSupportEnabled && ( + {globalNewCodeDefinition && branchSupportEnabled && (

{translate('project_baseline.configure_branches')}

@@ -330,14 +363,14 @@ class ProjectNewCodeDefinitionApp extends React.PureComponent { branchList={branchList} component={component} inheritedSetting={ - currentSetting + newCodeDefinitionType ? { - type: currentSetting, - value: currentSettingValue, + type: newCodeDefinitionType, + value: newCodeDefinitionValue, } - : generalSetting + : globalNewCodeDefinition } - generalSetting={generalSetting} + globalNewCodeDefinition={globalNewCodeDefinition} />
)} diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/components/ProjectNewCodeDefinitionSelector.tsx b/server/sonar-web/src/main/js/apps/projectNewCode/components/ProjectNewCodeDefinitionSelector.tsx index 204f4105bf3..0aac108102e 100644 --- a/server/sonar-web/src/main/js/apps/projectNewCode/components/ProjectNewCodeDefinitionSelector.tsx +++ b/server/sonar-web/src/main/js/apps/projectNewCode/components/ProjectNewCodeDefinitionSelector.tsx @@ -27,6 +27,7 @@ import GlobalNewCodeDefinitionDescription from '../../../components/new-code-def import NewCodeDefinitionDaysOption from '../../../components/new-code-definition/NewCodeDefinitionDaysOption'; import NewCodeDefinitionPreviousVersionOption from '../../../components/new-code-definition/NewCodeDefinitionPreviousVersionOption'; import NewCodeDefinitionWarning from '../../../components/new-code-definition/NewCodeDefinitionWarning'; +import { NewCodeDefinitionLevels } from '../../../components/new-code-definition/utils'; import { Alert } from '../../../components/ui/Alert'; import Spinner from '../../../components/ui/Spinner'; import { translate } from '../../../helpers/l10n'; @@ -45,10 +46,12 @@ export interface ProjectBaselineSelectorProps { branchesEnabled?: boolean; canAdmin: boolean | undefined; component: string; - currentSetting?: NewCodeDefinitionType; - currentSettingValue?: string; + newCodeDefinitionType?: NewCodeDefinitionType; + newCodeDefinitionValue?: string; + previousNonCompliantValue?: string; + projectNcdUpdatedAt?: number; days: string; - generalSetting: NewCodeDefinition; + globalNewCodeDefinition: NewCodeDefinition; isChanged: boolean; onCancel: () => void; onSelectDays: (value: string) => void; @@ -58,8 +61,8 @@ export interface ProjectBaselineSelectorProps { onToggleSpecificSetting: (selection: boolean) => void; referenceBranch?: string; saving: boolean; - selected?: NewCodeDefinitionType; - overrideGeneralSetting: boolean; + selectedNewCodeDefinitionType?: NewCodeDefinitionType; + overrideGlobalNewCodeDefinition: boolean; } function branchToOption(b: Branch) { @@ -74,24 +77,26 @@ export default function ProjectNewCodeDefinitionSelector(props: ProjectBaselineS branchesEnabled, canAdmin, component, - currentSetting, - currentSettingValue, + newCodeDefinitionType, + newCodeDefinitionValue, + previousNonCompliantValue, + projectNcdUpdatedAt, days, - generalSetting, + globalNewCodeDefinition, isChanged, - overrideGeneralSetting, + overrideGlobalNewCodeDefinition, referenceBranch, saving, - selected, + selectedNewCodeDefinitionType, } = props; - const isGlobalNcdCompliant = isNewCodeDefinitionCompliant(generalSetting); + const isGlobalNcdCompliant = isNewCodeDefinitionCompliant(globalNewCodeDefinition); const isValid = validateSetting({ - days, - overrideGeneralSetting, + numberOfDays: days, + overrideGlobalNewCodeDefinition, referenceBranch, - selected, + selectedNewCodeDefinitionType, }); if (branch === undefined) { @@ -102,7 +107,7 @@ export default function ProjectNewCodeDefinitionSelector(props: ProjectBaselineS
props.onToggleSpecificSetting(false)} @@ -121,14 +126,14 @@ export default function ProjectNewCodeDefinitionSelector(props: ProjectBaselineS
props.onToggleSpecificSetting(true)} value="specific" @@ -139,51 +144,67 @@ export default function ProjectNewCodeDefinitionSelector(props: ProjectBaselineS
{branchesEnabled && ( )} - {!branchesEnabled && currentSetting === NewCodeDefinitionType.SpecificAnalysis && ( + {!branchesEnabled && newCodeDefinitionType === NewCodeDefinitionType.SpecificAnalysis && ( )}
{!branchesEnabled && - overrideGeneralSetting && - selected === NewCodeDefinitionType.SpecificAnalysis && ( + overrideGlobalNewCodeDefinition && + selectedNewCodeDefinitionType === NewCodeDefinitionType.SpecificAnalysis && ( { const state = { analysis: 'analysis', - days: '35', + numberOfDays: '35', referenceBranch: 'branch-4.2', }; it('should work for Days', () => { expect(getSettingValue({ ...state, type: NewCodeDefinitionType.NumberOfDays })).toBe( - state.days + state.numberOfDays ); }); @@ -54,62 +54,64 @@ describe('getSettingValue', () => { describe('validateSettings', () => { it('should validate at branch level', () => { - expect(validateSetting({ days: '' })).toEqual(false); + expect(validateSetting({ numberOfDays: '' })).toEqual(false); expect( validateSetting({ - days: '12', - selected: NewCodeDefinitionType.NumberOfDays, + numberOfDays: '12', + selectedNewCodeDefinitionType: NewCodeDefinitionType.NumberOfDays, }) ).toEqual(true); expect( validateSetting({ - days: 'nope', - selected: NewCodeDefinitionType.NumberOfDays, + numberOfDays: 'nope', + selectedNewCodeDefinitionType: NewCodeDefinitionType.NumberOfDays, }) ).toEqual(false); expect( validateSetting({ - days: '', - selected: NewCodeDefinitionType.SpecificAnalysis, + numberOfDays: '', + selectedNewCodeDefinitionType: NewCodeDefinitionType.SpecificAnalysis, }) ).toEqual(false); expect( validateSetting({ - days: '', + numberOfDays: '', referenceBranch: 'master', - selected: NewCodeDefinitionType.ReferenceBranch, + selectedNewCodeDefinitionType: NewCodeDefinitionType.ReferenceBranch, }) ).toEqual(true); expect( validateSetting({ - days: '', + numberOfDays: '', referenceBranch: '', - selected: NewCodeDefinitionType.ReferenceBranch, + selectedNewCodeDefinitionType: NewCodeDefinitionType.ReferenceBranch, }) ).toEqual(false); }); it('should validate at project level', () => { - expect(validateSetting({ days: '', overrideGeneralSetting: false })).toEqual(true); + expect(validateSetting({ numberOfDays: '', overrideGlobalNewCodeDefinition: false })).toEqual( + true + ); expect( validateSetting({ - selected: NewCodeDefinitionType.PreviousVersion, - days: '', - overrideGeneralSetting: true, + selectedNewCodeDefinitionType: NewCodeDefinitionType.PreviousVersion, + numberOfDays: '', + overrideGlobalNewCodeDefinition: true, }) ).toEqual(true); expect( validateSetting({ - selected: NewCodeDefinitionType.NumberOfDays, - days: '', - overrideGeneralSetting: true, + selectedNewCodeDefinitionType: NewCodeDefinitionType.NumberOfDays, + numberOfDays: '', + overrideGlobalNewCodeDefinition: true, }) ).toEqual(false); expect( validateSetting({ - selected: NewCodeDefinitionType.NumberOfDays, - days: '12', - overrideGeneralSetting: true, + selectedNewCodeDefinitionType: NewCodeDefinitionType.NumberOfDays, + numberOfDays: '12', + overrideGlobalNewCodeDefinition: true, }) ).toEqual(true); }); diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/utils.ts b/server/sonar-web/src/main/js/apps/projectNewCode/utils.ts index 45c5c8efd94..1f859034f38 100644 --- a/server/sonar-web/src/main/js/apps/projectNewCode/utils.ts +++ b/server/sonar-web/src/main/js/apps/projectNewCode/utils.ts @@ -22,18 +22,18 @@ import { NewCodeDefinitionType } from '../../types/new-code-definition'; export function getSettingValue({ analysis, - days, + numberOfDays, referenceBranch, type, }: { analysis?: string; - days?: string; + numberOfDays?: string; referenceBranch?: string; type?: NewCodeDefinitionType; }) { switch (type) { case NewCodeDefinitionType.NumberOfDays: - return days; + return numberOfDays; case NewCodeDefinitionType.ReferenceBranch: return referenceBranch; case NewCodeDefinitionType.SpecificAnalysis: @@ -44,20 +44,26 @@ export function getSettingValue({ } export function validateSetting(state: { - days: string; - overrideGeneralSetting?: boolean; + numberOfDays: string; + overrideGlobalNewCodeDefinition?: boolean; referenceBranch?: string; - selected?: NewCodeDefinitionType; + selectedNewCodeDefinitionType?: NewCodeDefinitionType; }) { - const { days, overrideGeneralSetting, referenceBranch = '', selected } = state; + const { + numberOfDays, + overrideGlobalNewCodeDefinition, + referenceBranch = '', + selectedNewCodeDefinitionType, + } = state; return ( - overrideGeneralSetting === false || - (!!selected && + overrideGlobalNewCodeDefinition === false || + (!!selectedNewCodeDefinitionType && isNewCodeDefinitionCompliant({ - type: selected, - value: days, + type: selectedNewCodeDefinitionType, + value: numberOfDays, }) && - (selected !== NewCodeDefinitionType.ReferenceBranch || referenceBranch.length > 0)) + (selectedNewCodeDefinitionType !== NewCodeDefinitionType.ReferenceBranch || + referenceBranch.length > 0)) ); } diff --git a/server/sonar-web/src/main/js/apps/settings/components/NewCodeDefinition.tsx b/server/sonar-web/src/main/js/apps/settings/components/NewCodeDefinition.tsx index de5639a1a39..a2144e436b7 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/NewCodeDefinition.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/NewCodeDefinition.tsx @@ -26,6 +26,7 @@ import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon'; import NewCodeDefinitionDaysOption from '../../../components/new-code-definition/NewCodeDefinitionDaysOption'; import NewCodeDefinitionPreviousVersionOption from '../../../components/new-code-definition/NewCodeDefinitionPreviousVersionOption'; import NewCodeDefinitionWarning from '../../../components/new-code-definition/NewCodeDefinitionWarning'; +import { NewCodeDefinitionLevels } from '../../../components/new-code-definition/utils'; import Spinner from '../../../components/ui/Spinner'; import { translate } from '../../../helpers/l10n'; import { @@ -42,6 +43,7 @@ interface State { loading: boolean; currentSettingValue?: string; isChanged: boolean; + projectKey?: string; saving: boolean; selected?: NewCodeDefinitionType; success: boolean; @@ -68,7 +70,7 @@ export default class NewCodeDefinition extends React.PureComponent<{}, State> { fetchNewCodePeriodSetting() { getNewCodeDefinition() - .then(({ type, value, previousNonCompliantValue, updatedAt }) => { + .then(({ type, value, previousNonCompliantValue, projectKey, updatedAt }) => { this.setState(({ days }) => ({ currentSetting: type, days: type === NewCodeDefinitionType.NumberOfDays ? String(value) : days, @@ -76,6 +78,7 @@ export default class NewCodeDefinition extends React.PureComponent<{}, State> { currentSettingValue: value, selected: type, previousNonCompliantValue, + projectKey, ncdUpdatedAt: updatedAt, })); }) @@ -124,6 +127,8 @@ export default class NewCodeDefinition extends React.PureComponent<{}, State> { saving: false, currentSetting: type, currentSettingValue: value || undefined, + previousNonCompliantValue: undefined, + ncdUpdatedAt: Date.now(), isChanged: false, success: true, }); @@ -148,6 +153,7 @@ export default class NewCodeDefinition extends React.PureComponent<{}, State> { loading, isChanged, currentSettingValue, + projectKey, saving, selected, success, @@ -214,19 +220,26 @@ export default class NewCodeDefinition extends React.PureComponent<{}, State> { {isChanged && (
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodeDefinition-it.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodeDefinition-it.tsx index 422a08419ed..def197bba02 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodeDefinition-it.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodeDefinition-it.tsx @@ -50,7 +50,7 @@ const ui = { daysInput: byRole('spinbutton') /* spinbutton is the default role for a number input */, saveButton: byRole('button', { name: 'save' }), cancelButton: byRole('button', { name: 'cancel' }), - ncdAutoUpdateMessage: byText(/new_code_definition.auto_update.global.page.message/), + ncdAutoUpdateMessage: byText(/new_code_definition.auto_update.ncd_page.message/), ncdAutoUpdateMessageDismiss: byLabelText('alert.dismiss'), }; diff --git a/server/sonar-web/src/main/js/components/new-code-definition/NCDAutoUpdateMessage.tsx b/server/sonar-web/src/main/js/components/new-code-definition/NCDAutoUpdateMessage.tsx index e9386faeb7b..cb6d5c95c3f 100644 --- a/server/sonar-web/src/main/js/components/new-code-definition/NCDAutoUpdateMessage.tsx +++ b/server/sonar-web/src/main/js/components/new-code-definition/NCDAutoUpdateMessage.tsx @@ -122,7 +122,12 @@ function NCDAutoUpdateMessage(props: NCDAutoUpdateMessageProps) { : 'new_code_definition.auto_update.project.message'; return ( - + void; onSelect: (selection: NewCodeDefinitionType) => void; selected: boolean; + settingLevel: NewCodeDefinitionLevels; } export default function NewCodeDefinitionDaysOption(props: Props) { const { className, days, + currentDaysValue, previousNonCompliantValue, + projectKey, updatedAt, disabled, isChanged, @@ -58,22 +64,41 @@ export default function NewCodeDefinitionDaysOption(props: Props) { onChangeDays, onSelect, selected, + settingLevel, } = props; const [ncdAutoUpdateBannerDismissed, setNcdAutoUpdateBannerDismissed] = useState(true); useEffect(() => { async function fetchMessageDismissed() { - const messageStatus = await checkMessageDismissed({ - messageType: MessageTypes.GlobalNcdPage90, - }); + const messageStatus = await checkMessageDismissed( + projectKey + ? { + messageType: MessageTypes.ProjectNcdPage90, + projectKey, + } + : { + messageType: MessageTypes.GlobalNcdPage90, + } + ); setNcdAutoUpdateBannerDismissed(messageStatus.dismissed); } if (isDefined(previousNonCompliantValue)) { fetchMessageDismissed().catch(noop); } - }, [previousNonCompliantValue]); + }, [previousNonCompliantValue, projectKey, settingLevel]); + + const shouldShowAutoUpdateBanner = useMemo(() => { + return ( + (settingLevel === NewCodeDefinitionLevels.Global || + settingLevel === NewCodeDefinitionLevels.Project) && + isDefined(previousNonCompliantValue) && + isDefined(updatedAt) && + !disabled && + !ncdAutoUpdateBannerDismissed + ); + }, [disabled, ncdAutoUpdateBannerDismissed, previousNonCompliantValue, settingLevel, updatedAt]); const handleBannerDismiss = useCallback(async () => { await setMessageDismissed({ messageType: MessageTypes.GlobalNcdPage90 }); @@ -118,32 +143,30 @@ export default function NewCodeDefinitionDaysOption(props: Props) { )} - {isDefined(previousNonCompliantValue) && - isDefined(updatedAt) && - !ncdAutoUpdateBannerDismissed && ( - - - {translate('learn_more')} - - ), - }} - /> - - )} + {shouldShowAutoUpdateBanner && ( + + + {translate('learn_more')} + + ), + }} + /> + + )}
)} diff --git a/server/sonar-web/src/main/js/components/new-code-definition/NewCodeDefinitionSelector.tsx b/server/sonar-web/src/main/js/components/new-code-definition/NewCodeDefinitionSelector.tsx index 2b2b7587c0f..59f0d4eee6c 100644 --- a/server/sonar-web/src/main/js/components/new-code-definition/NewCodeDefinitionSelector.tsx +++ b/server/sonar-web/src/main/js/components/new-code-definition/NewCodeDefinitionSelector.tsx @@ -42,6 +42,7 @@ import Tooltip from '../controls/Tooltip'; import GlobalNewCodeDefinitionDescription from './GlobalNewCodeDefinitionDescription'; import NewCodeDefinitionDaysOption from './NewCodeDefinitionDaysOption'; import NewCodeDefinitionPreviousVersionOption from './NewCodeDefinitionPreviousVersionOption'; +import { NewCodeDefinitionLevels } from './utils'; interface Props { canAdmin: boolean | undefined; @@ -174,6 +175,7 @@ export default function NewCodeDefinitionSelector(props: Props) { onChangeDays={setDays} onSelect={handleNcdChanged} selected={selectedNcdType === NewCodeDefinitionType.NumberOfDays} + settingLevel={NewCodeDefinitionLevels.NewProject} /> ; } export default function NewCodeDefinitionWarning({ @@ -73,7 +74,9 @@ export default function NewCodeDefinitionWarning({

{translate( `baseline.number_days.compliance_warning.content.${level}${ - isBranchSupportEnabled && level === 'project' ? '.with_branch_support' : '' + isBranchSupportEnabled && level === NewCodeDefinitionLevels.Project + ? '.with_branch_support' + : '' }` )}

diff --git a/server/sonar-web/src/main/js/components/new-code-definition/utils.ts b/server/sonar-web/src/main/js/components/new-code-definition/utils.ts index 7545834f3a1..95cf53fc6f4 100644 --- a/server/sonar-web/src/main/js/components/new-code-definition/utils.ts +++ b/server/sonar-web/src/main/js/components/new-code-definition/utils.ts @@ -24,6 +24,13 @@ import { Permissions } from '../../types/permissions'; import { Component } from '../../types/types'; import { CurrentUser, isLoggedIn } from '../../types/users'; +export enum NewCodeDefinitionLevels { + Global = 'GLOBAL', + Project = 'PROJECT', + Branch = 'BRANCH', + NewProject = 'NEW_PROJECT', +} + export type PreviouslyNonCompliantNCD = NewCodeDefinition & Required>; diff --git a/server/sonar-web/src/main/js/components/ui/DismissableAlertComponent.tsx b/server/sonar-web/src/main/js/components/ui/DismissableAlertComponent.tsx index 202e91a8dd5..20a4a595da2 100644 --- a/server/sonar-web/src/main/js/components/ui/DismissableAlertComponent.tsx +++ b/server/sonar-web/src/main/js/components/ui/DismissableAlertComponent.tsx @@ -25,17 +25,22 @@ import ClearIcon from '../icons/ClearIcon'; import { Alert, AlertProps } from './Alert'; export interface DismissableAlertComponentProps extends AlertProps { + bannerClassName?: string; className?: string; children: React.ReactNode; onDismiss: () => void; } export default function DismissableAlertComponent(props: DismissableAlertComponentProps) { - const { className, display = 'banner', variant, children, onDismiss } = props; + const { bannerClassName, className, display = 'banner', variant, children, onDismiss } = props; return (
- +
{children}
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index c4c946108f3..cb846a32df7 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -3962,7 +3962,7 @@ new_code_definition.reference_branch.usecase=Recommended for projects using feat new_code_definition.reference_branch.notice=The main branch will be set as the reference branch when the project is created. You will be able to choose another branch as the reference branch when your project will have more branches. new_code_definition.auto_update.global.message=The global new code definition was automatically changed from {previousDays} to {days} days on {date}, following a SonarQube upgrade, as it was exceeding the maximum value. {link} -new_code_definition.auto_update.global.page.message=The number of days was automatically changed from {previousDays} to {days} on {date}, following a SonarQube upgrade, as it was exceeding the maximum value. {link} +new_code_definition.auto_update.ncd_page.message=The number of days was automatically changed from {previousDays} to {days} on {date}, following a SonarQube upgrade, as it was exceeding the maximum value. {link} new_code_definition.auto_update.project.message=This project's new code definition was automatically changed from {previousDays} to {days} days on {date}, following a SonarQube upgrade, as it was exceeding the maximum value. {link} new_code_definition.auto_update.review_link=Review new code definition -- 2.39.5