@@ -17,6 +17,7 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { HeadingDark, LargeCenteredLayout, PageContentFontWrapper, Spinner } from 'design-system'; | |||
import React, { useCallback, useEffect, useMemo, useState } from 'react'; | |||
import { Helmet } from 'react-helmet-async'; | |||
@@ -61,6 +62,7 @@ function ProjectNewCodeDefinitionApp(props: Readonly<ProjectNewCodeDefinitionApp | |||
const [numberOfDays, setNumberOfDays] = useState(getNumberOfDaysDefaultValue()); | |||
const [referenceBranch, setReferenceBranch] = useState<string | undefined>(undefined); | |||
const [specificAnalysis, setSpecificAnalysis] = useState<string | undefined>(undefined); | |||
const [selectedNewCodeDefinitionType, setSelectedNewCodeDefinitionType] = | |||
useState<NewCodeDefinitionType>(DEFAULT_NEW_CODE_DEFINITION_TYPE); | |||
@@ -68,6 +70,7 @@ function ProjectNewCodeDefinitionApp(props: Readonly<ProjectNewCodeDefinitionApp | |||
data: globalNewCodeDefinition = { type: DEFAULT_NEW_CODE_DEFINITION_TYPE }, | |||
isLoading: isGlobalNCDLoading, | |||
} = useNewCodeDefinitionQuery(); | |||
const { data: projectNewCodeDefinition, isLoading: isProjectNCDLoading } = | |||
useNewCodeDefinitionQuery({ | |||
branchName: hasFeature(Feature.BranchSupport) ? undefined : branchLike?.name, | |||
@@ -78,10 +81,12 @@ function ProjectNewCodeDefinitionApp(props: Readonly<ProjectNewCodeDefinitionApp | |||
const branchList = useMemo(() => { | |||
return sortBranches(branchLikes.filter(isBranch)); | |||
}, [branchLikes]); | |||
const isFormTouched = useMemo(() => { | |||
if (isSpecificNewCodeDefinition === undefined) { | |||
return false; | |||
} | |||
if (isSpecificNewCodeDefinition !== !projectNewCodeDefinition?.inherited) { | |||
return true; | |||
} | |||
@@ -97,10 +102,13 @@ function ProjectNewCodeDefinitionApp(props: Readonly<ProjectNewCodeDefinitionApp | |||
switch (selectedNewCodeDefinitionType) { | |||
case NewCodeDefinitionType.NumberOfDays: | |||
return numberOfDays !== String(projectNewCodeDefinition?.value); | |||
case NewCodeDefinitionType.ReferenceBranch: | |||
return referenceBranch !== projectNewCodeDefinition?.value; | |||
case NewCodeDefinitionType.SpecificAnalysis: | |||
return specificAnalysis !== projectNewCodeDefinition?.value; | |||
default: | |||
return false; | |||
} | |||
@@ -121,15 +129,19 @@ function ProjectNewCodeDefinitionApp(props: Readonly<ProjectNewCodeDefinitionApp | |||
setIsSpecificNewCodeDefinition( | |||
projectNewCodeDefinition === undefined ? undefined : !projectNewCodeDefinition.inherited, | |||
); | |||
setSelectedNewCodeDefinitionType( | |||
projectNewCodeDefinition?.type ?? DEFAULT_NEW_CODE_DEFINITION_TYPE, | |||
); | |||
setNumberOfDays(getNumberOfDaysDefaultValue(globalNewCodeDefinition, projectNewCodeDefinition)); | |||
setReferenceBranch( | |||
projectNewCodeDefinition?.type === NewCodeDefinitionType.ReferenceBranch | |||
? projectNewCodeDefinition.value | |||
: defaultReferenceBranch, | |||
); | |||
setSpecificAnalysis( | |||
projectNewCodeDefinition?.type === NewCodeDefinitionType.SpecificAnalysis | |||
? projectNewCodeDefinition.value | |||
@@ -180,19 +192,16 @@ function ProjectNewCodeDefinitionApp(props: Readonly<ProjectNewCodeDefinitionApp | |||
return ( | |||
<LargeCenteredLayout id="new-code-rules-page"> | |||
<Suggestions suggestions="project_baseline" /> | |||
<Helmet defer={false} title={translate('project_baseline.page')} /> | |||
<PageContentFontWrapper className="sw-my-8 sw-body-sm"> | |||
<AppHeader canAdmin={!!appState.canAdmin} /> | |||
<Spinner loading={isLoading} /> | |||
{!isLoading && ( | |||
<div className="it__project-baseline"> | |||
{branchSupportEnabled && ( | |||
<HeadingDark as="h3" className="sw-mt-4"> | |||
{translate('project_baseline.default_setting')} | |||
</HeadingDark> | |||
)} | |||
{globalNewCodeDefinition && isSpecificNewCodeDefinition !== undefined && ( | |||
<ProjectNewCodeDefinitionSelector | |||
analysis={specificAnalysis} | |||
@@ -225,6 +234,7 @@ function ProjectNewCodeDefinitionApp(props: Readonly<ProjectNewCodeDefinitionApp | |||
<HeadingDark as="h3" className="sw-mb-4"> | |||
{translate('project_baseline.configure_branches')} | |||
</HeadingDark> | |||
<BranchList | |||
branchList={branchList} | |||
component={component} |
@@ -17,6 +17,7 @@ | |||
* 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, ButtonSecondary, FlagMessage, RadioButton, Spinner } from 'design-system'; | |||
import { noop } from 'lodash'; | |||
import * as React from 'react'; | |||
@@ -34,26 +35,26 @@ import NewCodeDefinitionSettingReferenceBranch from './NewCodeDefinitionSettingR | |||
export interface ProjectBaselineSelectorProps { | |||
analysis?: string; | |||
branch?: Branch; | |||
branchList: Branch[]; | |||
branchesEnabled?: boolean; | |||
branchList: Branch[]; | |||
component: string; | |||
newCodeDefinitionType?: NewCodeDefinitionType; | |||
newCodeDefinitionValue?: string; | |||
previousNonCompliantValue?: string; | |||
projectNcdUpdatedAt?: number; | |||
days: string; | |||
globalNewCodeDefinition: NewCodeDefinition; | |||
isChanged: boolean; | |||
newCodeDefinitionType?: NewCodeDefinitionType; | |||
newCodeDefinitionValue?: string; | |||
onCancel: () => void; | |||
onSelectDays: (value: string) => void; | |||
onSelectReferenceBranch: (value: string) => void; | |||
onSelectSetting: (value: NewCodeDefinitionType) => void; | |||
onSubmit: (e: React.SyntheticEvent<HTMLFormElement>) => void; | |||
onToggleSpecificSetting: (selection: boolean) => void; | |||
overrideGlobalNewCodeDefinition: boolean; | |||
previousNonCompliantValue?: string; | |||
projectNcdUpdatedAt?: number; | |||
referenceBranch?: string; | |||
saving: boolean; | |||
selectedNewCodeDefinitionType?: NewCodeDefinitionType; | |||
overrideGlobalNewCodeDefinition: boolean; | |||
} | |||
function branchToOption(b: Branch) { | |||
@@ -66,17 +67,17 @@ export default function ProjectNewCodeDefinitionSelector( | |||
const { | |||
analysis, | |||
branch, | |||
branchList, | |||
branchesEnabled, | |||
branchList, | |||
component, | |||
newCodeDefinitionType, | |||
newCodeDefinitionValue, | |||
previousNonCompliantValue, | |||
projectNcdUpdatedAt, | |||
days, | |||
globalNewCodeDefinition, | |||
isChanged, | |||
newCodeDefinitionType, | |||
newCodeDefinitionValue, | |||
overrideGlobalNewCodeDefinition, | |||
previousNonCompliantValue, | |||
projectNcdUpdatedAt, | |||
referenceBranch, | |||
saving, | |||
selectedNewCodeDefinitionType, | |||
@@ -105,7 +106,7 @@ export default function ProjectNewCodeDefinitionSelector( | |||
<span>{translate('project_baseline.global_setting')}</span> | |||
</RadioButton> | |||
<div className="sw-ml-4"> | |||
<div className="sw-ml-6"> | |||
<GlobalNewCodeDefinitionDescription globalNcd={globalNewCodeDefinition} /> | |||
</div> | |||
@@ -128,26 +129,28 @@ export default function ProjectNewCodeDefinitionSelector( | |||
selectedNewCodeDefinitionType === NewCodeDefinitionType.PreviousVersion | |||
} | |||
/> | |||
<NewCodeDefinitionDaysOption | |||
days={days} | |||
currentDaysValue={ | |||
newCodeDefinitionType === NewCodeDefinitionType.NumberOfDays | |||
? newCodeDefinitionValue | |||
: undefined | |||
} | |||
previousNonCompliantValue={previousNonCompliantValue} | |||
updatedAt={projectNcdUpdatedAt} | |||
days={days} | |||
disabled={!overrideGlobalNewCodeDefinition} | |||
isChanged={isChanged} | |||
isValid={isValid} | |||
onChangeDays={props.onSelectDays} | |||
onSelect={props.onSelectSetting} | |||
previousNonCompliantValue={previousNonCompliantValue} | |||
selected={ | |||
overrideGlobalNewCodeDefinition && | |||
selectedNewCodeDefinitionType === NewCodeDefinitionType.NumberOfDays | |||
} | |||
settingLevel={NewCodeDefinitionLevels.Project} | |||
updatedAt={projectNcdUpdatedAt} | |||
/> | |||
{branchesEnabled && ( | |||
<NewCodeDefinitionSettingReferenceBranch | |||
branchList={branchList.map(branchToOption)} | |||
@@ -162,12 +165,13 @@ export default function ProjectNewCodeDefinitionSelector( | |||
settingLevel={NewCodeDefinitionLevels.Project} | |||
/> | |||
)} | |||
{!branchesEnabled && newCodeDefinitionType === NewCodeDefinitionType.SpecificAnalysis && ( | |||
<NewCodeDefinitionSettingAnalysis | |||
onSelect={noop} | |||
analysis={analysis ?? ''} | |||
branch={branch.name} | |||
component={component} | |||
onSelect={noop} | |||
selected={ | |||
overrideGlobalNewCodeDefinition && | |||
selectedNewCodeDefinitionType === NewCodeDefinitionType.SpecificAnalysis | |||
@@ -175,16 +179,19 @@ export default function ProjectNewCodeDefinitionSelector( | |||
/> | |||
)} | |||
</div> | |||
<div className="sw-mt-4"> | |||
{isChanged && ( | |||
<FlagMessage variant="info" className="sw-mb-4"> | |||
{translate('baseline.next_analysis_notice')} | |||
</FlagMessage> | |||
)} | |||
<div className="sw-flex sw-items-center"> | |||
<ButtonPrimary type="submit" disabled={!isValid || !isChanged || saving}> | |||
{translate('save')} | |||
</ButtonPrimary> | |||
<ButtonSecondary | |||
className="sw-ml-2" | |||
disabled={saving || !isChanged} | |||
@@ -192,6 +199,7 @@ export default function ProjectNewCodeDefinitionSelector( | |||
> | |||
{translate('cancel')} | |||
</ButtonSecondary> | |||
<Spinner className="sw-ml-2" loading={saving} /> | |||
</div> | |||
</div> |
@@ -66,8 +66,6 @@ it('renders correctly without branch support feature', async () => { | |||
// User is not admin | |||
expect(ui.generalSettingsLink.query()).not.toBeInTheDocument(); | |||
// Specific branch setting is not rendered without feature branch | |||
expect(ui.branchListHeading.query()).not.toBeInTheDocument(); | |||
expect(ui.referenceBranchRadio.query()).not.toBeInTheDocument(); | |||
}); | |||
@@ -85,8 +83,6 @@ it('renders correctly with branch support feature', async () => { | |||
// User is admin | |||
expect(ui.generalSettingsLink.get()).toBeInTheDocument(); | |||
// Specific branch setting is rendered with feature support branch | |||
expect(ui.branchListHeading.get()).toBeInTheDocument(); | |||
expect(ui.referenceBranchRadio.get()).toBeInTheDocument(); | |||
}); | |||
@@ -381,7 +377,6 @@ function getPageObjects() { | |||
const ui = { | |||
pageHeading: byRole('heading', { name: 'project_baseline.page' }), | |||
branchTableHeading: byText('branch_list.branch'), | |||
branchListHeading: byRole('heading', { name: 'project_baseline.default_setting' }), | |||
generalSettingsLink: byRole('link', { name: 'project_baseline.page.description2.link' }), | |||
generalSettingRadio: byRole('radio', { name: 'project_baseline.global_setting' }), | |||
specificSettingRadio: byRole('radio', { name: 'project_baseline.specific_setting' }), |
@@ -17,6 +17,8 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { TextSubdued } from 'design-system'; | |||
import * as React from 'react'; | |||
import { translate, translateWithParameters } from '../../helpers/l10n'; | |||
import { NewCodeDefinition, NewCodeDefinitionType } from '../../types/new-code-definition'; | |||
@@ -29,11 +31,13 @@ export default function GlobalNewCodeDefinitionDescription({ globalNcd }: Readon | |||
let setting: string; | |||
let description: string; | |||
let useCase: string; | |||
if (globalNcd.type === NewCodeDefinitionType.NumberOfDays) { | |||
setting = `${translate('new_code_definition.number_days')} (${translateWithParameters( | |||
'duration.days', | |||
globalNcd.value ?? '?', | |||
)})`; | |||
description = translate('new_code_definition.number_days.description'); | |||
useCase = translate('new_code_definition.number_days.usecase'); | |||
} else { | |||
@@ -44,9 +48,17 @@ export default function GlobalNewCodeDefinitionDescription({ globalNcd }: Readon | |||
return ( | |||
<div className="sw-flex sw-flex-col sw-gap-2 sw-max-w-[800px]"> | |||
<strong className="sw-font-bold">{setting}</strong> | |||
<span>{description}</span> | |||
<span>{useCase}</span> | |||
<TextSubdued> | |||
<strong className="sw-font-bold">{setting}</strong> | |||
</TextSubdued> | |||
<TextSubdued> | |||
<span>{description}</span> | |||
</TextSubdued> | |||
<TextSubdued> | |||
<span>{useCase}</span> | |||
</TextSubdued> | |||
</div> | |||
); | |||
} |
@@ -669,7 +669,6 @@ project_baseline.page.description=The new code definition sets which part of you | |||
project_baseline.page.description2=You can adjust this setting globally in {link}. | |||
project_baseline.page.description2.link=General Settings | |||
project_baseline.page.question=Choose the baseline for new code for this project | |||
project_baseline.default_setting=Project setting | |||
project_baseline.global_setting=Use the global setting | |||
project_baseline.specific_setting=Define a specific setting for this project | |||
project_baseline.configure_branches=Set a specific setting for a branch |