* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import withAvailableFeatures, {
+ WithAvailableFeaturesProps,
+} from '../../../app/components/available-features/withAvailableFeatures';
import RadioCard from '../../../components/controls/RadioCard';
-import ValidationInput from '../../../components/controls/ValidationInput';
+import ValidationInput, {
+ ValidationInputErrorPlacement,
+} from '../../../components/controls/ValidationInput';
+import { Alert } from '../../../components/ui/Alert';
import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation';
-import { translate } from '../../../helpers/l10n';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { MAX_NUMBER_OF_DAYS, MIN_NUMBER_OF_DAYS } from '../../../helpers/periods';
+import { Feature } from '../../../types/features';
import { NewCodePeriodSettingType } from '../../../types/types';
-export interface Props {
+export interface Props extends WithAvailableFeaturesProps {
className?: string;
days: string;
disabled?: boolean;
onChangeDays: (value: string) => void;
onSelect: (selection: NewCodePeriodSettingType) => void;
selected: boolean;
+ settingLevel: BaselineSettingDaysSettingLevel;
}
-export default function BaselineSettingDays(props: Props) {
- const { className, days, disabled, isChanged, isValid, onChangeDays, onSelect, selected } = props;
+export enum BaselineSettingDaysSettingLevel {
+ Global = 'global',
+ Project = 'project',
+ Branch = 'branch',
+}
+
+function BaselineSettingDays(props: Props) {
+ const {
+ className,
+ days,
+ disabled,
+ isChanged,
+ isValid,
+ onChangeDays,
+ onSelect,
+ selected,
+ settingLevel,
+ } = props;
+ const isBranchSupportEnabled = props.hasFeature(Feature.BranchSupport);
+
return (
<RadioCard
className={className}
<MandatoryFieldsExplanation />
<ValidationInput
- error={undefined}
labelHtmlFor="baseline_number_of_days"
- isInvalid={isChanged && !isValid}
+ isInvalid={!isValid}
isValid={isChanged && isValid}
+ errorPlacement={ValidationInputErrorPlacement.Bottom}
+ error={translateWithParameters(
+ 'baseline.number_days.invalid',
+ MIN_NUMBER_OF_DAYS,
+ MAX_NUMBER_OF_DAYS
+ )}
label={translate('baseline.specify_days')}
required={true}
>
<input
+ id="baseline_number_of_days"
onChange={(e) => onChangeDays(e.currentTarget.value)}
type="text"
value={days}
/>
</ValidationInput>
+
+ {!isChanged && !isValid && (
+ <Alert variant="warning" className="sw-mt-2">
+ <p className="sw-mb-2 sw-font-bold">
+ {translate('baseline.number_days.compliance_warning.title')}
+ </p>
+ <p className="sw-mb-2">
+ {translate(
+ `baseline.number_days.compliance_warning.content.${settingLevel}${
+ isBranchSupportEnabled &&
+ settingLevel === BaselineSettingDaysSettingLevel.Project
+ ? '.with_branch_support'
+ : ''
+ }`
+ )}
+ </p>
+ </Alert>
+ )}
</>
)}
</>
</RadioCard>
);
}
+
+export default withAvailableFeatures(BaselineSettingDays);
import { NewCodePeriod, NewCodePeriodSettingType } from '../../../types/types';
import { getSettingValue, validateSetting } from '../utils';
import BaselineSettingAnalysis from './BaselineSettingAnalysis';
-import BaselineSettingDays from './BaselineSettingDays';
+import BaselineSettingDays, { BaselineSettingDaysSettingLevel } from './BaselineSettingDays';
import BaselineSettingPreviousVersion from './BaselineSettingPreviousVersion';
import BaselineSettingReferenceBranch from './BaselineSettingReferenceBranch';
import BranchAnalysisList from './BranchAnalysisList';
onChangeDays={this.handleSelectDays}
onSelect={this.handleSelectSetting}
selected={selected === NewCodePeriodSettingType.NUMBER_OF_DAYS}
+ settingLevel={BaselineSettingDaysSettingLevel.Branch}
/>
<BaselineSettingAnalysis
onSelect={this.handleSelectSetting}
import { NewCodePeriod, NewCodePeriodSettingType } from '../../../types/types';
import { validateSetting } from '../utils';
import BaselineSettingAnalysis from './BaselineSettingAnalysis';
-import BaselineSettingDays from './BaselineSettingDays';
+import BaselineSettingDays, { BaselineSettingDaysSettingLevel } from './BaselineSettingDays';
import BaselineSettingPreviousVersion from './BaselineSettingPreviousVersion';
import BaselineSettingReferenceBranch from './BaselineSettingReferenceBranch';
import BranchAnalysisList from './BranchAnalysisList';
selected={
overrideGeneralSetting && selected === NewCodePeriodSettingType.NUMBER_OF_DAYS
}
+ settingLevel={BaselineSettingDaysSettingLevel.Project}
/>
{branchesEnabled ? (
<BaselineSettingReferenceBranch
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { isNewCodeDefinitionCompliant } from '../../helpers/periods';
import { NewCodePeriodSettingType } from '../../types/types';
-export function validateDays(days: string) {
- const parsed = parseInt(days, 10);
-
- return !(days.length < 1 || isNaN(parsed) || parsed < 1 || String(parsed) !== days);
-}
-
export function getSettingValue({
analysis,
days,
overrideGeneralSetting === false ||
selected === NewCodePeriodSettingType.PREVIOUS_VERSION ||
(selected === NewCodePeriodSettingType.SPECIFIC_ANALYSIS && analysis.length > 0) ||
- (selected === NewCodePeriodSettingType.NUMBER_OF_DAYS && validateDays(days)) ||
+ (selected === NewCodePeriodSettingType.NUMBER_OF_DAYS &&
+ isNewCodeDefinitionCompliant({
+ type: NewCodePeriodSettingType.NUMBER_OF_DAYS,
+ value: days,
+ })) ||
(selected === NewCodePeriodSettingType.REFERENCE_BRANCH && referenceBranch.length > 0);
return { isChanged, isValid };
import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { translate } from '../../../helpers/l10n';
+import { isNewCodeDefinitionCompliant } from '../../../helpers/periods';
import { NewCodePeriodSettingType } from '../../../types/types';
-import BaselineSettingDays from '../../projectBaseline/components/BaselineSettingDays';
+import BaselineSettingDays, {
+ BaselineSettingDaysSettingLevel,
+} from '../../projectBaseline/components/BaselineSettingDays';
import BaselineSettingPreviousVersion from '../../projectBaseline/components/BaselineSettingPreviousVersion';
-import { validateDays } from '../../projectBaseline/utils';
interface State {
currentSetting?: NewCodePeriodSettingType;
(selected === NewCodePeriodSettingType.NUMBER_OF_DAYS &&
String(days) !== currentSettingValue);
- const isValid = selected !== NewCodePeriodSettingType.NUMBER_OF_DAYS || validateDays(days);
+ const isValid =
+ selected !== NewCodePeriodSettingType.NUMBER_OF_DAYS ||
+ isNewCodeDefinitionCompliant({ type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: days });
return (
<>
</div>
<div className="settings-definition-right">
- {loading ? (
- <DeferredSpinner />
- ) : (
+ <DeferredSpinner loading={loading} timeout={500}>
<form onSubmit={this.onSubmit}>
<BaselineSettingPreviousVersion
isDefault={true}
onChangeDays={this.onSelectDays}
onSelect={this.onSelectSetting}
selected={selected === NewCodePeriodSettingType.NUMBER_OF_DAYS}
+ settingLevel={BaselineSettingDaysSettingLevel.Global}
/>
{isChanged && (
<div className="big-spacer-top">
</div>
)}
</form>
- )}
+ </DeferredSpinner>
</div>
</div>
</li>
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import * as React from 'react';
import { byRole, byText } from 'testing-library-selector';
import NewCodePeriodsServiceMock from '../../../../api/mocks/NewCodePeriodsServiceMock';
import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import { NewCodePeriodSettingType } from '../../../../types/types';
import NewCodePeriod from '../NewCodePeriod';
let newCodeMock: NewCodePeriodsServiceMock;
savedMsg: byText('settings.state.saved'),
prevVersionRadio: byRole('radio', { name: /baseline.previous_version/ }),
daysNumberRadio: byRole('radio', { name: /baseline.number_days/ }),
+ daysNumberErrorMessage: byText('baseline.number_days.invalid', { exact: false }),
daysInput: byRole('textbox'),
saveButton: byRole('button', { name: 'save' }),
cancelButton: byRole('button', { name: 'cancel' }),
await user.clear(ui.daysInput.get());
await user.type(ui.daysInput.get(), 'asdas');
expect(ui.saveButton.get()).toBeDisabled();
- await user.clear(ui.daysInput.get());
// Save enabled for valid days number
+ await user.clear(ui.daysInput.get());
await user.type(ui.daysInput.get(), '10');
expect(ui.saveButton.get()).toBeEnabled();
// Can save change
await user.click(ui.daysNumberRadio.get());
+ await user.clear(ui.daysInput.get());
await user.type(ui.daysInput.get(), '10');
await user.click(ui.saveButton.get());
expect(ui.savedMsg.get()).toBeInTheDocument();
expect(ui.savedMsg.get()).toBeInTheDocument();
});
+it('renders and behaves properly when the current value is not compliant', async () => {
+ const user = userEvent.setup();
+ newCodeMock.setNewCodePeriod({ type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: '91' });
+ renderNewCodePeriod();
+
+ expect(await ui.newCodeTitle.find()).toBeInTheDocument();
+ expect(ui.daysNumberRadio.get()).toBeChecked();
+ expect(ui.daysInput.get()).toHaveValue('91');
+
+ // Should warn about non compliant value
+ expect(ui.daysNumberErrorMessage.get()).toBeInTheDocument();
+ expect(screen.getByRole('alert')).toHaveTextContent(
+ 'baseline.number_days.compliance_warning.title'
+ );
+
+ await user.clear(ui.daysInput.get());
+ await user.type(ui.daysInput.get(), '92');
+
+ expect(screen.queryByRole('alert')).not.toBeInTheDocument();
+ expect(ui.daysNumberErrorMessage.get()).toBeInTheDocument();
+});
+
function renderNewCodePeriod() {
return renderComponent(<NewCodePeriod />);
}
it.each([
[{ type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: '0' }, false],
[{ type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: '15' }, true],
+ [{ type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: '15.3' }, false],
[{ type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: '91' }, false],
[{ type: NewCodePeriodSettingType.PREVIOUS_VERSION }, true],
[{ type: NewCodePeriodSettingType.REFERENCE_BRANCH }, true],
return (period as ApplicationPeriod).project !== undefined;
}
-const MIN_NUMBER_OF_DAYS = 1;
-const MAX_NUMBER_OF_DAYS = 90;
+export const MIN_NUMBER_OF_DAYS = 1;
+export const MAX_NUMBER_OF_DAYS = 90;
export function isNewCodeDefinitionCompliant(newCodePeriod: NewCodePeriod) {
switch (newCodePeriod.type) {
case NewCodePeriodSettingType.NUMBER_OF_DAYS:
+ if (!newCodePeriod.value) {
+ return false;
+ }
return (
- newCodePeriod.value !== undefined &&
+ Number.isInteger(+newCodePeriod.value) &&
MIN_NUMBER_OF_DAYS <= +newCodePeriod.value &&
+newCodePeriod.value <= MAX_NUMBER_OF_DAYS
);
baseline.number_days=Number of days
baseline.number_days.usecase=Recommended for projects following continuous delivery.
baseline.number_days.description=Any code that has changed in the last x days is considered new code. If no action is taken on a new issue after x days, this issue will become part of the overall code.
+baseline.number_days.invalid=Please provide a whole number between {0} and {1}
+baseline.number_days.compliance_warning.title=Update the number of days to benefit from the Clean as You Code methodology
+baseline.number_days.compliance_warning.content.global=We recommend that you update this New Code definition so that new projects and existing projects that do not use a specific New Code definition benefit from the Clean as You Code methodology by default.
+baseline.number_days.compliance_warning.content.project=We recommend that you update this New Code definition so that your project benefits from the Clean as You Code methodology.
+baseline.number_days.compliance_warning.content.project.with_branch_support=We recommend that you update this New Code definition so that new branches and existing branches that do not use a specific New Code definition benefit from the Clean as You Code methodology by default.
+baseline.number_days.compliance_warning.content.branch=We recommend that you update this New Code definition so that your branch benefits from the Clean as You Code methodology.
baseline.specific_analysis=Specific analysis
baseline.specific_analysis.description=Choose an analysis as the baseline for the new code.
baseline.reference_branch=Reference branch